Cloning Fetch Request — Cheating the Browser

Pasindu Senanayake
ParallaxTec
Published in
2 min readJan 13, 2023

Well cloning the Fetch Request is easy, isn’t it? Fetch provides an out-of-the-box solution for that right? Well Yes and No.

Request.clone() does the job for the exact clone. But most of the time we don’t need it. What we need is to send the same request to a different resource (URL). So why don’t use the obvious solution? Use the constructor.

async function implicitClone(){
const request = new Request(api_url_old, {method:'POST', body:JSON.stringify({'test':'test'}), headers: new Headers({
'Content-Type': 'application/json'
})})
const clonedRequest = new Request(api_url,request);
await fetch(clonedRequest);
}

It works perfectly when there is no request body. But when there is a body current browsers are struggling. At least Chrome and Firefox. (Well that is like 80% of usage!). While Firefox silently drops the request body in the cloned request, Chrome complains even before sending the request. (For further information https://github.com/whatwg/fetch/issues/1486)

support.js:85 Uncaught (in promise) TypeError: Failed to construct 'Request': The `duplex` member must be specified for a request with a streaming body
at implicitClone (support.js:85:28)
at HTMLButtonElement.onclick (index.html:12:36)

So let’s try cloning the request explicitly.

async function explicitClone(){
const request = new Request(api_url_old, {method:'POST', body:JSON.stringify({'test':'test'}), headers: new Headers({
'Content-Type': 'application/json'
})})
const clonedRequest = new Request(api_url, {
method: request.method,
headers: request.headers,
body: request.body,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
mode: request.mode,
credentials: request.credentials,
cache: request.cache,
redirect: request.redirect,
integrity: request.integrity,
});
await fetch(clonedRequest);
}

Too Bad! Still, we have the same problem. Even though the fetch specification clearly describes the request cloning web browsers aren’t there yet.

The request body in Fetch is made of ReadableStream<Uint8Array>. We can explore Readable Stream to make a clone of it. Let’s bring the big guns.

async function getContentOfReadableStream(requestBody: ReadableStream<Uint8Array> | null):  Promise<Uint8Array | null>{

if(requestBody){
let isPending = true;
let arrayLegnth = 0;
const unit8Arrays = []
const reader = requestBody.getReader();
do{
const readableResults = await reader.read();

if(readableResults.value){
arrayLegnth += readableResults.value.length
unit8Arrays.push(readableResults.value)
}

isPending = !readableResults.done
}while(isPending)
const mergedArray = new Uint8Array(arrayLegnth);
unit8Arrays.forEach(array=> mergedArray.set(array))
return mergedArray;

}
return null;
}

async function explicitCloneEnhanced() {

const request = new Request(api_url_old, {method:'POST', body:JSON.stringify({'test':'test'}), headers: new Headers({
'Content-Type': 'application/json'
})})
const requestBody = await getContentOfReadableStream(request.clone().body)
const clonedRequest = new Request(api_url, {
method: request.method,
headers: request.headers,
body: requestBody,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
mode: request.mode,
credentials: request.credentials,
cache: request.cache,
redirect: request.redirect,
integrity: request.integrity,
});
await fetch(clonedRequest);

}

Yey! Firefox and Chrome don’t complain anymore. Well, there is nothing to complain about. Finally, we made everyone happy. The solution has been tested on multiple browsers across multiple versions with different types of request bodies and it works perfectly. Theoretically, it’s a bit slower than out-of-box solutions but it gets the job done.

Happy coding folks! ✌️

--

--

Pasindu Senanayake
ParallaxTec

Graduate of Department of Computer Science and Engineering at University of Moratuwa