How to set a Timeout when using node-fetch

Updated: December 29, 2023 By: Guest Contributor Post a comment

Introduction

With the advent of Node.js, server-side JavaScript has become a powerful tool for development of various applications. node-fetch is a lightweight module that enables making HTTP requests similarly to the Fetch API available in the browser. However, when performing HTTP requests, it’s crucial to handle scenarios where a request might take longer than expected. Implementing timeouts in such situations is a common practice. This article digs into different strategies to set a timeout when using node-fetch in your Node.js projects.

Basic Timeout Implementation

To start off with the most straightforward approach, you can use native JavaScript features, such as Promise.race, to set a timeout. The code below demonstrates a basic timeout setup:

const fetch = require('node-fetch');
const TIMOEOUT = 5000; // 5 seconds

const fetchWithTimeout = async (url) => {
  const timeout = new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(new Error('Request timed out'));
    }, TIMEOUT);
  });
  const response = await Promise.race([fetch(url), timeout]);
  return response;
};

fetchWithTimeout('https://example.com')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.log(error.message));

Using AbortController for Enhanced Control

In Node.js versions that support AbortController, a more elegant and control-oriented approach involves using this API in conjunction with node-fetch. Below is an expanded example:

const fetch = require('node-fetch');

const fetchUrlWithTimeout = async (url, timeout) => {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeout);

  try {
    const response = await fetch(url, { signal: controller.signal });
    clearTimeout(timeoutId);
    return response.json();
  } catch (error) {
    if (error.name === 'AbortError') {
      throw new Error('Response was aborted due to timeout');
    }
    throw error;
  }
};

fetchUrlWithTimeout('https://example.com', 5000)
  .then(data => console.log(data))
  .catch(error => console.log(error.message));

Advanced Strategies: Wrap node-fetch with a Timeout Feature

For a more advanced and reusable solution, you might want to create a wrapper around node-fetch that automatically integrates timeout. Such a wrapper could look something like this:

const fetch = require('node-fetch');

const fetchWithBuiltInTimeout = (url, options = {}) => {
  const { timeout = 5000, ...rest } = options;
  return new Promise((resolve, reject) => {
    const controller = new AbortController();
    const timer = setTimeout(() => {
      controller.abort();
      reject(new Error('Request timed out'));
    }, timeout);

    fetch(url, { ...rest, signal: controller.signal })
      .then(response => {
        clearTimeout(timer);
        resolve(response);
      })
      .catch(err => {
        if (err.name === 'AbortError') {
          reject(new Error('Response has been aborted.'));
        }
        reject(err);
      });
  });
};

fetchWithBuiltInTimeout('https://example.com', { timeout: 3000 })
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.log(error.message));

Considerations When Setting Timeouts

It’s essential to carefully choose your timeout duration. Setting it too short might lead to unnecessary request abortions, especially under poor network conditions. Conversely, a timeout that’s too lengthy might cause the server resources to be unnecessarily tied up, affecting the application performance.

When altering configurations in libraries such as node-fetch, it is vital to thoroughly test the results, especially in cases where the network environment is unpredictable. Numerous factors can influence the responsiveness of the HTTP requests — such as network latency, server workload, and data sizes. Always make certain to adapt your timeout settings to the characteristics of the environment in which your application operates.

Conclusion

In conclusion, setting a timeout when using node-fetch is a fundamental aspect of building robust and reliable applications. Whether you utilize simple JavaScript timeouts, the built-in cancellation capabilities of AbortController, or enhance your fetch functionality with custom wrapping functions, make sure to handle timeouts gracefully to improve your application’s user experience. Always test your configurations, prepare for different network conditions, and fine-tune your timeout settings based on real-world usage patterns. Implementing proper request timeout handling not only ensures better resource management, but it also provides a fallback for users in case of unexpected delays.