Axios timeout error

I have set axios.defaults.timeout = 1000; I stopped the server that provides me with the APIs. But it takes more than 1s to timeout after sending a request. This is how my request looks: import...

I have set axios.defaults.timeout = 1000;

I stopped the server that provides me with the APIs.

But it takes more than 1s to timeout after sending a request.

This is how my request looks:

import axios from 'axios';
axios.defaults.timeout = 1000;

return axios.post(`${ROOT_URL}/login/${role}`, creds).then((response) => {
      console.log(response);

        if(response.status === 200) {
          // If login was successful, set the token in local storage
          localStorage.setItem(`${role}_log_toks`, JSON.stringify(response.data));

          // Dispatch the success action
          dispatch(receiveLogin(response.data));

          return response;
        }
      }).catch(err => {
        console.log(err);
        // If there was a problem, we want to
        // dispatch the error condition
        if(err.data && err.status === 404) {
          dispatch(loginError(err.data));
        } else {
          dispatch(loginError('Please check your network connection and try again.'));
        }

        return err;
      });

I have also tried:

return axios.post(`${ROOT_URL}/login/${role}`, creds, {timeout: 1000}).then...

Axios doesn’t stop fetching and after 5 — 10 minutes it finally shows network error. I understand that there are other techniques to handle timeout but why doesn’t the timeout feature in axios work? What could be the reason that axios doesn’t stop fetching?

Axios version 0.9.1

EDIT:
As mentioned in the comments, I have also tried:

import axios from 'axios';

const httpClient = axios.create();

httpClient.defaults.timeout = 500;

return httpClient.post(`${ROOT_URL}/login/${role}`, creds)
  .then(handleResponse)

I solved the problem by adding timeout into the axios configuration before data parameter. For some reason, timeout parameter will not work if placed after the data parameter.

axios({
      method: 'post',
      timeout: 1000,
      url: 'http://192.168.0.1:5000/download',
      data: {
          access: data.token
      }
})
.then(function (response) {
    alert(response.data);
})
.catch(function (error) {
    alert("There was an error in communicating to server");
});

I have tried with both timeout before and after but same result, there’s a timeout but after ~1min (timeout is set to 100ms).
—> node test.js 0.13s user 0.05s system 0% cpu 1:15.47 total

I used @arfa123’s example code for testing.

this is the result:

time node test.js
running
end of the code not of the execution
{ Error: connect ETIMEDOUT 93.184.216.34:81
    at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1107:14)
  errno: 'ETIMEDOUT',
  code: 'ETIMEDOUT',
  syscall: 'connect',
  address: '93.184.216.34',
  port: 81,
  config:
   { url: 'http://example.com:81',
     method: 'post',
     data: '{"foo":"bar"}',
     headers:
      { Accept: 'application/json, text/plain, */*',
        'Content-Type': 'application/json;charset=utf-8',
        'User-Agent': 'axios/0.19.2',
        'Content-Length': 13 },
     transformRequest: [ [Function: transformRequest] ],
     transformResponse: [ [Function: transformResponse] ],
     timeout: 100,
     adapter: [Function: httpAdapter],
     xsrfCookieName: 'XSRF-TOKEN',
     xsrfHeaderName: 'X-XSRF-TOKEN',
     maxContentLength: -1,
     validateStatus: [Function: validateStatus] },
  request:
   Writable {... },
     writable: true,
     _events:
      [Object: null prototype] {
        response: [Function: handleResponse],
        error: [Function: handleRequestError] },
     _eventsCount: 2,
     _maxListeners: undefined,
     _options:
      { protocol: 'http:',
        maxRedirects: 21,
        maxBodyLength: 10485760,
        path: '/',
        method: 'POST',
        headers: [Object],
        agent: undefined,
        agents: [Object],
        auth: undefined,
        hostname: 'example.com',
        port: '81',
        nativeProtocols: [Object],
        pathname: '/' },
     _redirectCount: 0,
     _redirects: [],
     _requestBodyLength: 13,
     _requestBodyBuffers: [ [Object] ],
     _onNativeResponse: [Function],
     _currentRequest:
      ClientRequest {
        _events: [Object],
        _eventsCount: 6,
        _maxListeners: undefined,
        output: [],
        outputEncodings: [],
        outputCallbacks: [],
        outputSize: 0,
        writable: true,
        _last: true,
        chunkedEncoding: false,
        shouldKeepAlive: false,
        useChunkedEncodingByDefault: true,
        sendDate: false,
        _removedConnection: false,
        _removedContLen: false,
        _removedTE: false,
        _contentLength: null,
        _hasBody: true,
        _trailer: '',
        finished: false,
        _headerSent: true,
        socket: [Socket],
        connection: [Socket],
        _header:
         'POST / HTTP/1.1rnAccept: application/json, text/plain, */*rnContent-Type: application/json;charset=utf-8rnUser-Agent: axios/0.19.2rnContent-Length: 13rnHost: example.com:81rnConnection: closernrn',
        _onPendingData: [Function: noopPendingOutput],
        agent: [Agent],
        socketPath: undefined,
        timeout: undefined,
        method: 'POST',
        path: '/',
        _ended: false,
        res: null,
        aborted: undefined,
        timeoutCb: [Function: emitRequestTimeout],
        upgradeOrConnect: false,
        parser: null,
        maxHeadersCount: null,
        _redirectable: [Circular],
        [Symbol(isCorked)]: false,
        [Symbol(outHeadersKey)]: [Object] },
     _currentUrl: 'http://example.com:81/' },
  response: undefined,
  isAxiosError: true,
  toJSON: [Function] }
node test.js  0.13s user 0.05s system 0% cpu 1:15.47 total

Axios is one of the most popular JavaScript libraries to do HTTP requests. It is an HTTP client available for both the browser and Node.js. Setting Axios timeout properly makes your application perform efficiently even when the called API/URL is not responding in a timely manner. In this post, you will learn about Axios, its configs, and how to set Axios timeout properly to not hamper your application’s performance. Let’s get going.

Axios timeout illustration with a clock

Table of contents #

  • Calling other services/APIs
  • Why use a timeout in requests
  • Axios
    • Installing Axios
    • Axios request configs
    • Set Axios timeout example
    • Axios timeout in action
    • Example with Axios timeout added
  • Conclusion

Calling other services/APIs #

In today’s world of highly interconnected services and API economy, more often than not your application will call either external HTTP APIs or internal ones. Depending on how your company’s applications and the communication between them is architected, you are most likely calling some internal HTTP APIs. In addition to that, your applications might also be calling external APIs to accomplish all sorts of tasks from some AI-related calculation to creating shipments for a customer order.

Imagine this, your shipment application is calling the Auspost/DHL API to create a shipment and get an Airway Bill (AWB) number to send to the customer. Due to the last quarter of the year, the high traffic season of Black Friday and Christmas sales their API is responding extremely slowly. Usually, the create AWB HTTP API would respond in under 200 milliseconds (ms) but due to the load and ongoing issue at the time of calling it was responding in around 3 seconds.

This means that your warehouse (fulfillment center) staff are waiting for no direct benefit. In addition, as the API responses are flaky it is causing other issues too. This is where timeout on HTTP requests comes in handy, which is what is going to be discussed next.

Why use a timeout in requests #

If the requests made to the API endpoint to create and get the AWB does not have a timeout set. It would result in every request taking 3 seconds and that request may or may not pass. Without any reliable retry mechanism, the person trying to create the AWB/shipment would waste a lot of time and might even make other human errors.

In case a reasonable timeout was set for creating shipments (getting the AWB) on the courier’s HTTP API, this would have been less of an issue. Let’s say a 500-millisecond timeout was set, then the user would know that if they see a timeout error they can proceed with the next shipment.

Coupling that with a reliable retry mechanism that can notify the user when the AWB is created when the task is completed in the background would help reduce the impact of this problem to a certain degree. Like many other things, this is how HTTP APIs are called to create a shipment for your e-commerce order. In the next section, you will learn about Axios and how it can send HTTP requests to both the client and the server.

Axios #

Axois is a promise-based HTTP client that works for both Node.js and the browser. On the client (browser) side it uses XMLHttpRequest and on the server side, it utilizes the native Node.js http module. It also has very useful features like intercepting request and response data, and the ability to automatically transform request and response data to JSON.

Installing Axios #

Axios can be installed on any Node.js application that uses NPM with the following command:

npm i –save axios

The yarn equivalent of the above command is:

yarn add axios

Axios request configs #

There are various request config options for Axios like auth for basic auth, response type, max redirects, and timeout to name some. Among them, timeout is an important one. The timeout config is also vital because the default value is 0.

This means unless you set the timeout value explicitly, any request made using Axios is going to wait forever for a response.

This is dangerous as it can quickly spiral up to be a big performance issue regardless your app is calling an internal or 3rd party HTTP API. In the next section, you will learn how to set a timeout in an Axios request and why is it important.

Set Axios timeout example #

For this example, you will call an API that will respond after a certain number of milliseconds have passed. As the HTTP call goes through the network the usual latency of DNS, the server’s computation time, etc will also be added to the wait time sent as the data has to pass through the wires and the network. The code for the slow API you are going to call looks like the below:

const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
const {setTimeout} = require('timers/promises');

app.get('/', (req, res) => {
res.json({
message: `alive`,
});
});

app.get('/api/mock-response-time/:milliseconds', async (req, res) => {
console.log(`Api hit`, req.params);
const waitMs = req.params.milliseconds || 100;
await setTimeout(waitMs);
res.json({
message: `responded after waiting for ${req.params.milliseconds} milliseconds (ms)`
});
});

app.listen(port, () => {
console.log(`Slow API app listening on port ${port}`);
});

It is a very simple Express API. You start by requiring express and instantiating it. Then you set the port to be taken from the environment variable PORT or if it is not set fallback to 3000. After that, you require the setTimeout from the timers/promises package which is available from Node.js version 15+. It will be used to wait for X milliseconds, you can read more about wait in JavaScript if that is interesting to you.

Next, you add the home route / that responds with a stock JSON of {“message”: “alive”}. After that, another route is added /api/mock-response-time/:milliseconds where the caller will pass the number of milliseconds it wants the server to wait before sending a response back. As mentioned, the server will take some more time than the wait because of DNS, network, and other factors.

Finally your start the server with app.listen on the port and log that the server has started. The above code is available for your reference as a GitHub repository. It has also been deployed on Render (one of the free Node.js hosting services) at https://slow-api.onrender.com . You will call this a simple but useful endpoint to test out Axios timeout next.

Axios timeout in action #

Now, you will write some code using Axios to call the GET API that acts like a slow API depending on the values passed to the endpoint. First, you will call the API without setting any timeout with the code below:

const axios = require('axios');

console.log = (() => {
var console_log = console.log;
var timeStart = new Date().getTime();

return function() {
var delta = new Date().getTime() - timeStart;
var args = [];
args.push((delta / 1000).toFixed(2) + ':');
for(var i = 0; i < arguments.length; i++) {
args.push(arguments[i]);
}
console_log.apply(console, args);
};
})();

axios.interceptors.request.use( req => {
req.meta = req.meta || {}
req.meta.requestStartedAt = new Date().getTime();
return req;
});

axios.interceptors.response.use(res => {
console.log(`Execution time for: ${res.config.url} - ${ new Date().getTime() - res.config.meta.requestStartedAt} ms`)
return res;
},
res => {
console.error(`Execution time for: ${res.config.url} - ${new Date().getTime() - res.config.meta.requestStartedAt} ms`)
throw res;
});

(async () => {

try {
const eightSecondsinMs = 8000;
const url = `https://slow-api.onrender.com/api/mock-response-time/${eightSecondsinMs}`;
console.log(`Sending a GET reqeust to: ${url}`);
const response = await axios.get(url);
console.log(`Response: `, response?.data?.message );
console.log('do this after you have data');
} catch(err) {
console.log(`Error message : ${err.message} - `, err.code);
}

console.log('do the next task');
})();

You first started by requiring Axios. In lines 3-15 you have overloaded the console.log function to include the time elapsed between calls. For instance, if the first console.log is called and the next console.log is called after one second it will append the first one with 0.00 and the second one with 1.00. It has been taken from this old gist.

Next, you add two Axios Interceptors. Interceptors help you intercept the request and response objects and manipulate them before they are handled. The first will intercept the request and add the requestStartedAt value as the current time. It will add a log on the request with the time it was initiated. This will be used later to calculate the time it took to get the response back.

Then you add another interceptor to the response. This interceptor gets the current time and subtracts it from the requested stated time which results in the duration of time it took for the response to be received. It logs the time for the response to be fetched in milliseconds. The second interceptor on the response handles the case for both valid/successful status response of 200-299 or invalid status codes like 4XX and 5XX. For the error case, it logs with console.error and throws back the response.

Then there is an Immediately Invoked Function Expression (IIFE) to use async-await. As async await is not available at the top level yet. This is where the main thing happens. You set a URL to be called on the above slow API server with a delay of 8 seconds (8000 milliseconds). Then you call the URL with axios.get, notice here no timeout is set so you are instructing Axios to wait for as long as it takes to fetch the response. This wait forever (timeout: 0) is the default behavior for Axios.

After that, you log the message from the response data. As the whole call is in try catch in case of any error or a nonsuccessful response (for instance a 501), it will be logged in the catch section. After the try-catch, you do another log to denote that there is another task to be done after calling the slow API. When you run it you can see an output like the one below:

Output before Axios timeout - takes 8.76 seconds

In this case, 8.76 seconds feels like a long time to wait but finally, you get the output. As the timeout is not set Axios waited for as long as it took to get the response. This would have been detrimental if this task was part of another API call that was getting a significant amount of traffic as all the requests would be clogged up because some 3rd party service is responding really slowly. Next, you will see an example with Axios timeout implemented.

Example with Axios timeout added #

A snippet of the same example with timeout added looks like the following:

(async () => {

try {
const eightSecondsinMs = 8000;
const url = `https://slow-api.onrender.com/api/mock-response-time/${eightSecondsinMs}`;
console.log(`Sending a GET reqeust to: ${url}`);
const response = await axios.get(url, {timeout: 900});
console.log(`Response: `, response?.data?.message );
console.log('do this after you have data');
} catch(err) {
console.log(`Error message : ${err.message} - `, err.code);
}

console.log('do the next task');
})();

The code is exactly the same as the above, except now there is a timeout of 900 milliseconds added as highlighted. 900 ms is still high in normal scenarios but in this case, you know the API is going to respond in at least 8000 milliseconds. You can also run the checks concurrently with Javascript promise.all if you want. When you run the above code you will see the following:

Output after Axios timeout - takes 0.96 seconds

Here, the situation is much better because as the timeout was hit in 900 milliseconds the execution moved on. And the log for do the next task was visible before 1 second. In a real-life scenario, it would make the API fail fast in case the 3rd party API was slow. Depending on how you want to weave software resilience into your apps you can explore the retry mechanism or even try out the circuit breaker pattern. The code with and without a timeout to call the slow API endpoint is accessible to you as a Github repository.

Conclusion #

In this tutorial, you learned about Axios and how adding one simple config of Axios timeout can save you from an untoward situation. The moral of the story is:

Always add timeout when you call either internal or external APIs. You cannot control how long it takes for an external API to respond.

If the external API is down your service should respond and fail gracefully. It should not fail because some third-party API is not responding properly. A simple config like timeout will save you from unnecessary headaches. Don’t forget to add a timeout to every API call you make!

The idea behind timeouts is that in scenarios where a program has to wait for
something to happen (such as a response to an HTTP request), the waiting is
aborted if the operation cannot be completed within a specified duration. This
allows for a more efficient control of sever resources as stuck operations or
stalling connections are not allowed continued use of limited resources.

When writing servers in Node.js, the judicious use of timeouts when performing
I/O operations is crucial to ensuring that your application is more resilient to
external attacks driven by resource exhaustion (such as
Denial of Service (DoS) attacks)
and
Event Handler Poisoning attacks.
By following through with this tutorial, you will learn about the following
aspects of utilizing timeouts in a Node.js application:

  • Enforcing timeouts on client connections.
  • Canceling outgoing HTTP requests after a deadline.
  • Adding timeouts to Promises.
  • A strategy for choosing timeout values.

Prerequisites

To follow through with this tutorial, you need to have the latest version of
Node.js installed on your computer (v18.1.0 at the time of writing). You should
also clone the following
GitHub repository to
your computer to run the examples demonstrated in this tutorial:

git clone https://github.com/betterstack-community/nodejs-timeouts

After the project is downloaded, cd into the nodejs-timeouts directory and
run the command below to download all the necessary dependencies:

🔭 Want to centralize and monitor your Node.js logs?

Head over to Logtail and start ingesting your logs in 5 minutes.

Timeouts on incoming HTTP requests (Server timeouts)

Server timeouts typically refer to the timeout applied to incoming client
connections. This means that when a client connects to the server, the
connection is only maintained for a finite period of time before it is
terminated. This is handy when dealing with slow clients that are taking an
exceptionally long time to receive a response.

Node.js exposes a
server.timeout property that
determines the amount of inactivity on a connection socket before it is assumed
to have timed out. It is set to 0 by default which means no timeout, giving
the possibility of a connection that hangs forever.

To fix this, you must set server.timeout to a more suitable value:

http_server.js

const http = require('http');
const server = http.createServer((req, res) => {
  console.log('Got request');

  setTimeout(() => {
    res.end('Hello World!');
  }, 10000);
});

server.timeout = 5000;

server.listen(3000);

The above example sets the server timeout to 5 seconds so that inactive
connections (when no data is being transferred in either direction) are closed
once that timeout is reached. To demonstrate a timeout of this nature, the
function argument to http.createServer() has been configured to respond 10
seconds after a request has been received so that the timeout will take effect.

Go ahead and start the server, then make a GET request with curl:

curl http://localhost:3000

You should see the following output after 5 seconds, indicating that a response
was not received from the server due to a closed connection.

Output

curl: (52) Empty reply from server

If you need to do something else before closing the connection socket, then
ensure to listen for the timeout event on the server. The Node.js runtime
will pass the timed out socket to the callback function.

. . .
server.timeout = 5000;

server.on('timeout', (socket) => {

console.log('timeout');

socket.destroy();

});

. . .

Ensure to call socket.destroy() in the callback function so that the
connection is closed. Failure to do this will leave the connection open
indefinitely. You can also write the snippet above as follows:

server.setTimeout(5000, (socket) => {
  console.log('timeout');
  socket.destroy();
});

This method of setting server timeouts also works with Express servers:

express_server.js

const express = require('express');
const app = express();
const port = 3000;

app.get('/', (req, res) => {
  console.log('Got request');
  setTimeout(() => res.send('Hello world!'), 10000);
});

const server = app.listen(port, () => {
  console.log(`Example app listening on port ${port}`);
});

server.setTimeout(5000, (socket) => {
  console.log('timeout');
  socket.destroy();
});

If you want to override the server timeout on a particular route, use the
req.setTimeout() method as shown below:

app.get('/', (req, res) => {
  req.setTimeout(20000);
  console.log('Got request');
  setTimeout(() => res.send('Hello world!'), 10000);
});

This will cause requests to the site root to timeout after 20 seconds of
inactivity instead of the 5 second default.

Timeouts on outgoing HTTP requests (Client timeouts)

Setting timeouts on outgoing network requests is a crucial requirement that must
not be overlooked. Networks are unreliable, and third-party APIs are often prone
to slowdowns that could degrade your application’s performance significantly.
That’s why you should never send out a network request without knowing the
maximum time it will take. Therefore, this section will discuss how to set
timeouts on outgoing HTTP requests in Node.js.

Setting timeouts on the native request module

The native http.request() and https.request() methods in Node.js do not have
default timeouts nor a way to set one, but you can set a timeout value per
request quite easily through the options object.

request.js

const https = require('https');

const options = {
  host: 'icanhazdadjoke.com',
  path: '/',
  method: 'GET',
  headers: {
    Accept: 'application/json',
  },
  timeout: 2000,
};

const req = https.request(options, (res) => {
  res.setEncoding('utf8');

  let body = '';

  res.on('data', (chunk) => {
    body += chunk;
  });

  res.on('end', () => console.log(body));
});

req.on('error', (err) => {
  if (err.code == 'ECONNRESET') {
    console.log('timeout!');
    return;
  }

  console.error(err);
});

req.on('timeout', () => {
  req.destroy();
});

req.end();

The options object supports a timeout property that you can set to timeout a
request after a specified period has elapsed (two seconds in this case). You
also need to listen for a timeout event on the request and destroy the request
manually in its callback function. When a request is destroyed, an ECONNRESET
error will be emitted so you must handle it by listening for the error event
on the request. You can also emit your own error in destroy():

class TimeoutError extends Error {}
req.destroy(new TimeoutError('Timeout!'));

Instead of using the timeout property and timeout event as above, you can
also use the setTimeout() method on a request as follows:

const req = https
  .request(options, (res) => {
    res.setEncoding('utf8');

    let body = '';

    res.on('data', (chunk) => {
      body += chunk;
    });

    res.on('end', () => console.log(body));
  })

.setTimeout(2000, () => {

req.destroy();

});

Timing out a Fetch API request

The Fetch API was
recently merged into Node.js core
in Node.js v17.5, so you can start using it in your Node.js applications
provided you include the --experimental-fetch argument to the node command.

fetch.js

(async function getDadJoke() {
  try {
    const response = await fetch('https://icanhazdadjoke.com', {
      headers: {
        Accept: 'application/json',
      },
    });
    const json = await response.json();
    console.log(json);
  } catch (err) {
    console.error(err);
  }
})();
node fetch.js --experimental-fetch

You can omit the --experimental-fetch flag in Node.js v18 or higher:

Output

{
  id: '5wHexPC5hib',
  joke: 'I made a belt out of watches once... It was a waist of time.',
  status: 200
}

In browsers, fetch() usually times out after a set period of time which varies
amongst browsers. For example, in Firefox this timeout is set to 90 seconds by
default, but in Chromium, it is 300 seconds. In Node.js, no default timeout is
set for fetch() requests, but the newly added
AbortSignal.timeout()
API provides an easy way to cancel a fetch() request when a timeout is
reached.

cancel-fetch.js

(async function getDadJoke() {
  try {
    const response = await fetch('https://icanhazdadjoke.com', {
      headers: {
        Accept: 'application/json',
      },

signal: AbortSignal.timeout(3000),

}); const json = await response.json(); console.log(json); } catch (err) { console.error(err); } })();

In the above snippet, the AbortSignal.timeout() method cancels the fetch()
request if it doesn’t resolve within 3 seconds. You can test this out by setting
a low timeout value (like 2ms), then execute the script above. You should
notice that an AbortError is thrown and caught in the catch block:

Output

AbortError: The operation was aborted
    at abortFetch (node:internal/deps/undici/undici:5623:21)
    at requestObject.signal.addEventListener.once (node:internal/deps/undici/undici:5560:9)
    at [nodejs.internal.kHybridDispatch] (node:internal/event_target:639:20)
    at AbortSignal.dispatchEvent (node:internal/event_target:581:26)
    at abortSignal (node:internal/abort_controller:291:10)
    at AbortController.abort (node:internal/abort_controller:321:5)
    at AbortSignal.abort (node:internal/deps/undici/undici:4958:36)
    at [nodejs.internal.kHybridDispatch] (node:internal/event_target:639:20)
    at AbortSignal.dispatchEvent (node:internal/event_target:581:26)
    at abortSignal (node:internal/abort_controller:291:10) {
  code: 'ABORT_ERR'
}

If you’re using fetch() extensively in your code, you may want to create a
utility function that sets a default timeout on all fetch requests, but that can
be easily overridden if necessary.

fetch-with-timeout.js

async function fetchWithTimeout(resource, options = {}) {
  const { timeoutMS = 3000 } = options;

  const response = await fetch(resource, {
    ...options,
    signal: AbortSignal.timeout(timeoutMS),
  });

  return response;
}

. . .

The fetchWithTimeout() function above defines a default timeout of 3 seconds
on all fetch() requests created through it, but this can be easily overridden
by specifying the timeoutMS property in the options object. With this
function in place, the getDadJoke() function now looks like this assuming the
default timeout is used:

fetch-with-timeout.js

. . .

(async function getDadJoke() {
  try {
    const response = await fetchWithTimeout('https://icanhazdadjoke.com', {
      headers: {
        Accept: 'application/json',
      },
    });

    const json = await response.json();
    console.log(json);
  } catch (err) {
    console.error(err);
  }
})();

Now that we have looked at how to set timeouts on the native HTTP request APIs
in Node.js, let’s consider how to do the same when utilizing some of the most
popular third-party HTTP request libraries in the Node.js ecosystem.

🔭 Want to get alerted when your Node.js app stops working?

Head over to Better Uptime and start monitoring your endpoints in 2 minutes

Setting timeouts in Axios

The Axios package has a default timeout
of 0 which means no timeout, but you can easily change this value by setting a
new default:

const axios = require('axios');
axios.defaults.timeout = 5000;

With the above in place, all HTTP requests created by axios will wait up to 5
seconds before timing out. You can also override the default value per request
in the config object as shown below:

axios.js

(async function getPosts() {
  try {
    const response = await axios.get(
      'https://jsonplaceholder.typicode.com/posts',
      {
        headers: {
          Accept: 'application/json',
        },
        timeout: 2000,
      }
    );

    console.log(response.data);
  } catch (err) {
    console.error(err);
  }
})();

If you get a timeout error, it will register as ECONNABORTED in the catch
block.

Setting timeouts in Got

Got is another popular Node.js package for
making HTTP requests, but it also does not have a default timeout so you must
set one for yourself on each request:

got.js

const got = require('got');

(async function getPosts() {
  try {
    const data = await got('https://jsonplaceholder.typicode.com/posts', {
      headers: {
        Accept: 'application/json',
      },
      timeout: {
        request: 2000,
      },
    }).json();

    console.log(data);
  } catch (err) {
    console.error(err);
  }
})();

Ensure to check out the
relevant docs
for more information on timeouts in Got.

Adding timeouts to promises

Promises are the recommended way to perform asynchronous operations in Node.js,
but there is currently no API to cancel one if it is not fulfilled within a
period of time. This is usually not a problem since most async operations will
finish within a reasonable time, but it means that a pending promise can
potentially take a long time to resolve causing the underlying operation to slow
down or hang indefinitely.

Here’s an example that simulates a Promise that takes 10 seconds to resolve:

slow-ops.js

const timersPromises = require('timers/promises');

function slowOperation() {
  // resolve in 10 seconds
  return timersPromises.setTimeout(10000);
}

(async function doSomethingAsync() {
  try {
    await slowOperation();
    console.log('Completed slow operation');
  } catch (err) {
    console.error('Failed to complete slow operation due to error:', err);
  }
})();

In this example doSomethingAsync() will also take at least 10 seconds to
resolve since slowOperation() blocks for 10 seconds. If slowOperation()
hangs forever, doSomethingAsync() will also hang forever, and this is often
undesirable for a high performance server. The good news is we can control the
maximum time that we’re prepared to wait for slowOperation() to complete by
racing it with another promise that is resolved after a fixed amount of time.

The Promise.race() method receives an iterable object (usually as an Array)
that contains one or more promises, and it returns a promise that resolves to
the result of the first promise that is fulfilled, while the other promises in
the iterable are ignored. This means that the promise returned by
Promise.race() is settled with the same value as the first promise that
settles amongst the ones in the iterable.

This feature can help you implement Promise timeouts without utilizing any
third-party libraries.

promise-with-timeout.js

const timersPromises = require('timers/promises');

function slowOperation() {
  // resolve in 10 seconds
  return timersPromises.setTimeout(10000);
}

function promiseWithTimeout(promiseArg, timeoutMS) {

const timeoutPromise = new Promise((resolve, reject) =>

setTimeout(() => reject(`Timed out after ${timeoutMS} ms.`), timeoutMS)

);

return Promise.race([promiseArg, timeoutPromise]);

}

(async function doSomethingAsync() { try {

await promiseWithTimeout(slowOperation(), 2000);

console.log('Completed slow operation in 10 seconds'); } catch (err) { console.error('Failed to complete slow operation due to error:', err); } })();

The promiseWithTimeout() function takes a Promise as its first argument and
a millisecond value as its second argument. It creates a new Promise that
always rejects after the specified amount of time has elapsed, and races it with
the promiseArg, returning the pending Promise from Promise.race() to the
caller.

This means that if promiseArg takes more than the specified amount of time
(timeoutMS) to be fulfilled, timeoutPromise will reject and
promiseWithTimeout() will also reject with the value specified in
timeoutPromise.

We can see this in action in doSomethingAsync(). We’ve decided that
slowOperation() should be given a maximum of two seconds to complete. Since
slowOperation() always takes 10 seconds, it will miss the deadline so
promiseWithTimeout() will reject after 2 seconds and an error will be logged
to the console.

Output

Failed to complete slow operation due to error: Timed out after 2000 ms.

If you want to differentiate timeout errors from other types of errors
(recommended), you can create a TimeoutError class that extends the Error
class and reject with a new instance of TimeoutError as shown below:

timeout-error.js

const timersPromises = require('timers/promises');

function slowOperation() {
  // resolve in 10 seconds
  return timersPromises.setTimeout(10000);
}

class TimeoutError extends Error {

constructor(message) {

super(message);

this.name = this.constructor.name;

}

}

function promiseWithTimeout(promiseArg, timeoutMS) { const timeoutPromise = new Promise((resolve, reject) => setTimeout( () => reject(new TimeoutError(`Timed out after ${timeoutMS} ms.`)), timeoutMS ) ); return Promise.race([promiseArg, timeoutPromise]); } (async function doSomethingAsync() { try { await promiseWithTimeout(slowOperation(), 2000); console.log('Completed slow operation'); } catch (err) {

if (err instanceof TimeoutError) {

console.error('Slow operation timed out');

return;

}

console.error('Failed to complete slow operation due to error:', err); } })();

Running the script above should now give you a «Slow operation timed out»
message:

You will notice that the script above remains active until the 10-second
duration of slowOperation() has elapsed despite timing out after 2 seconds.
This is because the timersPromises.setTimeout() method used in
slowOperation() requires that the Node.js event loop remains active until the
scheduled time has elapsed. This is a waste of resources because the result has
already been discarded, so we need a way to ensure that scheduled Timeout is
also cancelled.

You can use the
AbortController
class to cancel the promisified setTimer() method as shown below:

abort-controller.js

. . .

function slowOperation() {

const ac = new AbortController();

return {

exec: () => timersPromises.setTimeout(10000, null, { signal: ac.signal }),

cancel: () => ac.abort(),

};

}

. . . (async function doSomethingAsync() {

const slowOps = slowOperation();

try {

await promiseWithTimeout(slowOps.exec(), 2000);

console.log('Completed slow operation'); } catch (err) { if (err instanceof TimeoutError) {

slowOps.cancel();

console.error('Slow operation timed out'); return; } console.error('Failed to complete slow operation due to error:', err); } })();

In slowOperation(), a new instance of AbortController is created and set on
the timer so that it can be canceled if necessary. Instead of returning the
Promise directly, we’re returning an object that contains two functions: one
to execute the promise, and the other to cancel the timer.

With these changes in place, doSomethingAsync() is updated so that the object
from slowOperation() is stored outside the try..catch block. This makes it
possible to access its properties in either block. The cancel() function is
executed in the catch block when a TimeoutError is detected to prevent
slowOperation() from consuming resources after timing out.

We also need a way to cancel the scheduled Timeout in promiseWithTimeout()
so that if the promise is settled before the timeout is reached, additional
resources are not being consumed by timeoutPromise.

cancel-timeout.js

. . .

function promiseWithTimeout(promiseArg, timeoutMS) {

let timeout;

const timeoutPromise = new Promise((resolve, reject) => {

timeout = setTimeout(() => {

reject(new TimeoutError(`Timed out after ${timeoutMS} ms.`));

}, timeoutMS);

});

return Promise.race([promiseArg, timeoutPromise]).finally(() =>

clearTimeout(timeout)

);

}

. . .

The promiseWithTimeout() option has been updated such that the Timeout value
returned by the global setTimeout() function is stored in a timeout
variable. This gives the ability to clear the timeout using the clearTimeout()
function in the finally() method attached to the return value of
Promise.race(). This ensures that the timer is canceled immediately the
promise settles.

You can observe the result of this change by modifying the timeout value in
slowOperation() to something like 200ms. You’ll notice that the script
prints a success message and exits immediately. Without canceling the timeout in
the finally() method, the script will continue to hang until the two seconds
have elapsed despite the fact that promiseArg has already been settled.

If you want to use this promiseWithTimeout() solution in
TypeScript, here are the appropriate types to use:

promise-with-timeout.ts

function promiseWithTimeout<T>(
  promiseArg: Promise<T>,
  timeoutMS: number
): Promise<T> {
  let timeout: NodeJS.Timeout;
  const timeoutPromise = new Promise<never>((resolve, reject) => {
    timeout = setTimeout(() => {
      reject(new TimeoutError(`Timed out after ${timeoutMS} ms.`));
    }, timeoutMS);
  });

  return Promise.race([promiseArg, timeoutPromise]).finally(() =>
    clearTimeout(timeout)
  );
}

In this snippet, promiseWithTimeout() is defined as a generic function that
accepts a generic type parameter T, which is what promiseArg resolves to.
The function’s return value is also a Promise that resolves to type T. We’ve
also set the return value of timeoutPromise to Promise<never> to reflect
that it will always reject.

How to choose a timeout value

So far, we’ve discussed various ways to set timeout values in Node.js. It is
equally important to figure out what the timeout value should be in a given
situation depending on the application and the operation that’s being performed.
A timeout value that is too low will lead to unnecessary errors, but one that is
too high may decrease application responsiveness when slowdowns or outages
occur, and increase susceptibility to malicious attacks.

Generally speaking, higher timeout values can be used for background or
scheduled tasks while immediate tasks should have shorter timeouts. Throughout
this post, we used arbitrary timeout values to demonstrate the concepts but
that’s not a good strategy for a resilient application. Therefore, it is
necessary to briefly discuss how you might go about this.

With external API calls, you can start by setting your timeouts to a high value
initially, then run a load test to gather some data about the API’s throughput,
latency, response times, and error rate under load. If you use a tool like
Artillery, you can easily gather such data, and
also find out the 95th and 99th percentile response times.

For example, if you have a 99th percentile response time of 500ms, it means that
99% of requests to such endpoint was fulfilled in 500ms or less. You can then
multiply the 99th percentile value by 3 or 4 to get a baseline timeout for that
specific endpoint. With such timeouts in place, you can be reasonably sure that
it should suffice for over 99% of requests to the endpoint.

That being said, it’s often necessary to refine the timeout value especially if
you start getting a high number of timeout errors, so make sure to have a
monitoring system in place for tracking such
metrics. It may also be necessary to set a timeout that is much greater than the
calculated baseline timeout when a critical operation is being performed (like a
payment transaction for example).

Conclusion

In this article, we discussed the importance of timeouts in Node.js, and how to
set timeouts in a variety of scenarios so that your application remains
responsive even when third-party APIs are experiencing slowdowns. We also
briefly touched on a simple process for how you might choose a timeout value for
an HTTP request, and the importance of monitoring and refining your timeout
values.

You can find all the code snippets used throughout this article in this
GitHub repository.
Thanks for reading, and happy coding!

Centralize all your logs into one place.

Analyze, correlate and filter logs with SQL.

Create actionable

dashboards.

Share and comment with built-in collaboration.

Got an article suggestion?
Let us know

Share on Twitter

Share on Facebook

Share via e-mail

Licensed under CC-BY-NC-SA

This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

Have you ever been stuck on what looks like an empty page, and you ask yourself, «Am I supposed to be seeing something yet?», only for it to appear like 30 seconds later? Or maybe you’ve clicked on a button and you’re not sure whether its processing or not (like on checkout pages). If thats what your own app feels like then, read on. In this guide, I’ll walk you through 4 scenarios you should handle when working with APIs using axios & react.

  • Handling requests that sometimes take longer than usual and leave the user looking at an empty page
  • Handling requests that have errored and you want to give the user a way out
  • Handling a possible timeout where the request is taking significantly longer than usual and giving the user an updated loading message so they see the page isn’t frozen
  • Handling a definite timeout where you want to abort the request to give the user a more specific error message

I’ll start with a very innocent example — When the ResultsList component loads, it request some data, and displays it in the UI.

const ResultsList = () => {
  const [results, setResults] = useState([])

  // run on load
  useEffect(() => {
    axios.get(apiUrl).then(response => {
      setResults(response.data)
    }).catch(err => {
      console.log(err)
    })
  }, [])

  return (
    <ul>
      { results.map(result => {
          return <li>{result.name}</li>
        })
      }
    </ul>
    )
  }

The data from the API is being stored in the results field of the component’s state. It starts as an empty array, and gets replaced with the actual results when the data is fetched.

This is buggy. Here’s why.

1. Handling long response times

Not all users will have a great connection, even if your backend is stable, and that request may take a little longer to complete than usual. In this example, there’s no feedback to the user that something is happening, and when there are issues, the page will be empty for a lot longer than you’re expecting.

It will make your users ask, «Am I supposed to be seeing something yet?»

This question can be solved with a few snippets:

const ResultsList = () => {
+  const [results, setResults] = useState(null)

...

+  const getListItems = () => {
+    if(results) {
+      return results.map(result => {
+        return <li>{result.name}</li>
+    })
+    } else {
+      return (
+        <div>
+          <i class="fas fa-spinner fa-spin"></i>
+          Results are loading...
+       </div>
+      )
+    }
+   }
+
+  return (
+    <div>
+      <ul>{getListItems()}</ul>
+    </div>
+    )
  }

There are 2 changes

  • Rather than initialize results to an empty array [], its initialized to null.
  • I can then check if I should show a loading message, or if I should show the list with data.

Pro-tip: Add spinners. They’re so easy. And fun. Your users will be less confused. I say this as someone who was too lazy to add spinners.

2. Handling errors

When something goes wrong, the easy options are to write it to the console, or the show user an error message. The best error message is one that can tell the user how to fix whatever just happened. I have more details on the axios error object on this post here

The easy option is to show the user something useful on any error, and offer them a way to fix things.

const ResultsList = () => {
  const [results, setResults] = useState(null)
+ const [error, setError] = useState(null)

+ const loadData = () => {
+   return axios.get(apiUrl).then(response => {
+     setResults(response.data)
+     setError(null)
+   }).catch(err => {
+     setError(err)
+   })
+ }

  // run on load
  useEffect(() => {
+   loadData()
-    ...
  }, [])

+ const getErrorView = () => {
+   return (
+     <div>
+       Oh no! Something went wrong.
+       <button onClick={() => loadData()}>
+         Try again
+.      </button>
+     </div>
+   )
+ }

  return (
    <div>
+    <ul>
+      {  error ? 
+        getListItems() : getErrorView() 
+      }
+    </ul>
-    <ul>{getListItems()}</ul>
    </div>
    )
  }

Here, I’ve added an error field to the component’s state where I can keep track of errors and conditionally show an error message to the user. I give them a painfully generic error message, but I offer a way out via a button to Try again which will retry the request.

When the request succeeds, the error is cleared from the state, and the user sees the right info. Wonderful.

3. Handling a possible timeout

You get extra bonus points if you add this. Its not that its hard, its just that its quite considerate. Every once in awhile, a user will come across a loading spinner, and they’ll wonder — «Is it frozen and the icon is just spinning or does it really take this long?»

If a request is taking awhile, you can give the user a little feedback in the form of, «This is taking longer than usual…»

+ const TIMEOUT_INTERVAL = 60 * 1000

const loadData = () => {
+   if (results) setResults(null)

    // make the request
    axios.get(apiUrl).then(response => {
      setResults(response.data)
      setError(null)
    }).catch(err => {
      setError(err)
    })

+   // show an update after a timeout period
+   setTimeout(() => {
+     if (!results && !error) {
+       setError(new Error('Timeout'))
+     }
+   }, TIMEOUT_INTERVAL)
}

const getErrorView = () => {
+   if (error.message === 'Timeout') {
+     <div>This is taking longer than usual</div>
+   } else {
      return (
        <div>
          Oh no! Something went wrong.
          <button onClick={() => loadData()}>
           Try again
          </button>
        </div>
      )
+    }
}

You can set a timer for whichever TIMEOUT_INTERVAL you want. When the timer executes, check whether the data has already loaded, and show the extra error message. If there are no results yet, and no errors, we’re still waiting for data so you can show the updated loading message.

4. Handling a definite timeout

If you know a request is supposed to be quick, and you want to give the user quick feedback, you can set a timeout in the axios config.

const loadData = () => {
    if (results) setResults(null)

    // make the request
+    axios.get(apiUrl, { timeout: TIMEOUT_INTERVAL }).then(response => {
-    axios.get(apiUrl).then(response => {
      setResults(response.data)
      setError(null)
    }).catch(err => {
      setError(err)
    })
        ...
}
const getErrorView = () => {
    if (error.message === 'Timeout') {
      <div>This is taking longer than usual</div>
+    } else if (error.message.includes('timeout')) {
+      <div>This is taking too long. Something is wrong - try again later.</div>
+    } else {
      return ...
    }
}

Axios will throw an error here when the request rejects and it’ll have an error message like timeout of 30000ms exceeded

Get started

Take a look at your React projects that are working with APIs and review how you’re handling errors. These tips are an easy way to give your users with a more resilient experience. If you’ve tried it out or have different ways of approaching your errors, let me know in the comments!

Sr Software Developer and Engineering Manager at an edtech company writing about leading teams, automated testing, and building sites hosted on AWS.

Понравилась статья? Поделить с друзьями:
  • Axios react native network error
  • Axios post cors error
  • Axios post catch error
  • Axios json error
  • Axios interceptor error