Understanding Promise.race in Node.js: Practical Explanation

🎓
This episode is a part of the Learn JavaScript Promises collection and is #3 episode of them all.

Introduction

Promises are a powerful tool in JavaScript for handling asynchronous operations. One of the most useful features of Promises is the ability to handle multiple promises at once with the Promise.race() method. This method allows you to start multiple promises and return the first one that resolves or rejects. In this article, we'll take a look at how to use Promise.race() in Node.js, including some real-world examples and additional details about those examples.


Step 1: Create your Promises

The first step in using Promise.race() is to create the promises that you want to race against each other. These can be any type of promise, including those created with Promise.resolve() or Promise.reject(). For example:

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('Promise 1 resolved');
  }, 1000);
});

const promise2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('Promise 2 resolved');
  }, 2000);
});

const promise3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('Promise 3 rejected');
  }, 1500);
});

Step 2: Use Promise.race()

Once you have your promises created, you can use the Promise.race() method to start them all at once. This method takes an array of promises as its argument and returns a new promise that resolves or rejects with the first promise to do so. For example:

Promise.race([promise1, promise2, promise3])
  .then((result) => {
    console.log(result);
  })
  .catch((error) => {
    console.log(error);
  });

In this example: promise1promise2, and promise3 are passed as an array to Promise.race(). The promise returned by Promise.race() will resolve with the value of the first promise to resolve. In this case, promise1 will be resolved and the output will be Promise 1 resolved after 1 sec.

Step 3: Handle the results

Finally, you'll want to handle the results of the promise returned by Promise.race(). You can do this using the .then() and .catch() methods, just like with any other promise. In the example above, we log the result to the console if the promise resolves, and the error if it rejects.

Step 4: Examples of using Promise.race() in a Node.js application

  • Example 1: Loading an image

Consider a scenario where you want to load an image from a remote server and a local cache. With Promise.race(), you can start loading the image from both sources at the same time and return the first one to finish.

const loadImageFromServer = (url) => {
  return new Promise((resolve, reject) => {
    const image = new Image();
    image.onload = () => resolve(image);
    image.onerror = () => reject(new Error('Failed to load image from server'));
    image.src = url;
  });
};

const loadImageFromCache = (url) => {
  return new Promise((resolve, reject) => {
    const image = localCache.get(url);
    if (image) {
      resolve(image);
    } else {
      reject(new Error('Image not found in cache'));
    }
  });
};

Promise.race([loadImageFromServer(imageUrl), loadImageFromCache(imageUrl)])
  .then((image) => {
    // Use the image
  })
  .catch((error) => {
    console.log(error);
  });

In this example: loadImageFromServer() and loadImageFromCache() are the two promises that we want to race against each other. The Promise.race() method is used to start both promises at the same time and return the first one to resolve. In this case, the loadImageFromCache() function will resolve and return the image if it is found in the cache, otherwise loadImageFromServer() will resolve and return the image after it is loaded from the server.

  • Example 2: Loading a page

Consider a scenario where you want to load a page from a remote server and a local cache. With Promise.race(), you can start loading the page from both sources at the same time and return the first one to finish.

const loadPageFromServer = (url) => {
  return new Promise((resolve, reject) => {
    request(url, (err, res, body) => {
      if (!err && res.statusCode === 200) {
        resolve(body);
      } else {
        reject(new Error('Failed to load page from server'));
      }
    });
  });
};

const loadPageFromCache = (url) => {
  return new Promise((resolve, reject) => {
    const page = localCache.get(url);
    if (page) {
      resolve(page);
    } else {
      reject(new Error('Page not found in cache'));
    }
  });
};

Promise.race([loadPageFromServer(pageUrl), loadPageFromCache(pageUrl)])
  .then((page) => {
    // Use the page
  })
  .catch((error) => {
    console.log(error);
  });

In this example: loadPageFromServer() and loadPageFromCache() are the two promises that we want to race against each other. The Promise.race() method is used to start both promises at the same time and return the first one to resolve. In this case, the loadPageFromCache() function will resolve and return the page if it is found in the cache, otherwise loadPageFromServer() will resolve and return the page after it is loaded from the server.

  • Example 3: Loading a User profile

Consider a scenario where you want to load a user profile from a remote server and a local cache. With Promise.race(), you can start loading the user profile from both sources at the same time and return the first one to finish.

const loadUserProfileFromServer = (userId) => {
  return new Promise((resolve, reject) => {
    db.users.find({ userId: userId }, (err, user) => {
      if (!err) {
        resolve(user);
      } else {
        reject(new Error('Failed to load user profile from server'));
      }
    });
  });
};

const loadUserProfileFromCache = (userId) => {
  return new Promise((resolve, reject) => {
    const user = localCache.get(userId);
    if (user) {
      resolve(user);
    } else {
      reject(new Error('User profile not found in cache'));
    }
  });
};

Promise.race([loadUserProfileFromServer(userId), loadUserProfileFromCache(userId)])
  .then((user) => {
    // Use the user profile
  })
  .catch((error) => {
    console.log(error);
  });

In this example: loadUserProfileFromServer() and loadUserProfileFromCache() are the two promises that we want to race against each other. The Promise.race() method is used to start both promises at the same time and return the first one to resolve. In this case, the loadUserProfileFromCache() function will resolve and return the user profile if it is found in the cache, otherwise loadUserProfileFromServer() will resolve and return the user profile after it is loaded from the server.

  • Example 4: Loading a file

Consider a scenario where you want to load a file from a remote server and a local cache. With Promise.race(), you can start loading the file from both sources at the same time and return the first one to finish.

const loadFileFromServer = (filePath) => {
  return new Promise((resolve, reject) => {
    fs.readFile(filePath, (err, data) => {
      if (!err) {
        resolve(data);
      } else {
        reject(new Error('Failed to load file from server'));
      }
    });
  });
};

const loadFileFromCache = (filePath) => {
  return new Promise((resolve, reject) => {
    const file = localCache.get(filePath);
    if (file) {
      resolve(file);
    } else {
      reject(new Error('File not found in cache'));
    }
  });
};

Promise.race([loadFileFromServer(filePath), loadFileFromCache(filePath)])
  .then((file) => {
    // Use the file
  })
  .catch((error) => {
    console.log(error);
  });

In this example: loadFileFromServer() and loadFileFromCache() are the two promises that we want to race against each other. The Promise.race() method is used to start both promises at the same time and return the first one to resolve. In this case, the loadFileFromCache() function will resolve and return the file if it is found in the cache, otherwise loadFileFromServer() will resolve and return the file after it is loaded from the server.


Conclusion

The Promise.race() method in Node.js is a powerful tool for handling asynchronous operations in your applications. By following the step-by-step instructions and examples provided in this article, you should now have a better understanding of how to use Promise.race() to start multiple promises at once and return the first one to resolve or reject. With this knowledge, you can use Promise.race() to optimize your code and improve the performance of your Node.js applications.