Adding Timeout to Fetch API

Publish date: 2020-01-16
Tags: javascript, react

By twahyono

The goal

We want to add timeout capability to already excellent Fetch API with something as simple as adding timeout parameter:

var resp = await fetch(someUrl);

to something like:

var resp = await fetch(someUrl,timeout);

Introducing AbortController

It allows you to abort one or more web request when desired by sending abort signal to Fetch API. To create AbortController object do the following:

var abortCtrl = new AbortController();
var signal = abortCtrl.signal(); // store the signal to be use in fetch request

To send the abort signal do following:

abortCtrl.abort()

Fetch request with signal parameter

Then we put signal as parameter in the fetch request, for example (replace api_key with your own key):

let url = "http://maps.openweathermap.org/maps/2.0/weather/TA2/{z}/{x}/{y}?date=1527811200&opacity=0.9&fill_bound=true&appid={api_key}";
let resp = await fetch(url,{signal})
let json = await resp.json();

When abort signal is received the fetch request will return reject promise with AbortError.

Adding timeout

OK now we want to have the capability if within specific amount of time the fetch request still not returning any data we want the timeout message cause we don’t want to wait any longer. For this purpose we can create two promises and running it in parallel and wait which ever promise finish first we will take the result.

let promise1 = fetch(someUrl, {signal});

let promise2 = new Promise(resolve=>{
    setTimeout(async () => {
            abortCtrl.abort() // abort fetch request
            resolve("timeout")
        }, 500) // timeout in 500ms
})

let result = await Promise.race([promise1, promise2]);

Above code will run promise1 and promise 2 in parallel. Promise.race methods will return only single promise result that finish the execution first. If promise1 finish first than promise 2 it will return the fetch data. If promise2 finish first it will cancel task promise1 and return string ‘timeout’.

Putting it all together

Let’s create new fetch function that has timeout parameter.

async function fetchWithTimeout(url, timeout) {
    let ctrl = new AbortController()
    let signal = ctrl.signal

    let fetchData = fetch(url, {signal})
        .then(resp => resp.json())
        .then(json=>json)
        .catch(err => {throw err}) // sent error stack trace

    // set timeout
    let timeOut = new Promise(resolve => {
        setTimeout(async () => {
            ctrl.abort() // abort fetch request
            resolve("timeout")
        }, timeout)
    })

    let result = await Promise.race([fetchData, timeOut])
    return result
}

Limitations

It will work with GET method only and it will return json object/data. In next article we will modify the function so it can accept POST method and return other data type.