Understanding Promises in Node.js
© https://nodejs.org/en/

Understanding Promises in Node.js

Learn how to create and work with Promises in Node.js

By Mario Kandut

Europe’s developer-focused job platform

Let companies apply to you

Developer-focused, salary and tech stack upfront.

Just one profile, no job applications!

A promise is a placeholder for a value that will be available in the future, so the result of an asynchronous task can be handled once it has finished. Promises make writing asynchronous code easier and are an improvement to the callback pattern (please google for callback hell). Since ES6 promises are a standard part of Javascript and with async/await (ES8) they are used in async functions .

This article is based on Node v16.14.0.

What are promises?

💰 The Pragmatic Programmer: journey to mastery. 💰 One of the best books in software development, sold over 200,000 times.

To understand Promises it is important to understand the difference between synchronous and asynchronous code first.

Synchronous code executes in the sequence it is written, code statements wait until the ones before them have finished. Hence, synchronous code is considered blocking in Node.js. Blocking could be in some rare cases considered useful, like reading important configuration on start-up before anything else runs, but the application is unresponsive until this synchronous task is finished. Therefore, not applicable on long-running tasks, like making an HTTP call.

Asynchronous code works by starting a task, and letting it complete in the background while other code is still able to execute. When the async code has completed, the handler function (callback) is immediately executed with the result from the async code. Hence, asynchronous code is non-blocking, because it does not prevent the rest of your code from executing, while the asynchronous task is running in the background. With async code, we don't know when or if the task will complete successfully. The callback of the async code will be called as soon as the result is available, or when an error has occurred.

Once you an async process has started, like an HTTP request, filesystem access, or something similar, you are given something that will notify the caller when that process has completed. A Promise is that "something". A promise is a placeholder for a value that will be available in the future.

Why use Promises?

Promises allow to handle the results of asynchronous code, like callbacks. Unlike callbacks, the async code with promises is easier to read, maintain, and reason about. Consider these examples, five consecutive API calls with error handling.

Promises

fetch('url')
  .then(() => fetch('url'))
  .then(() => fetch('url'))
  .then(() => fetch('url'))
  .then(() => fetch('url'))
  .then(() => console.log('all done'))
  .catch(err => console.log(err));

Callbacks

fetchCallback('url', err => {
  if (err) return console.log(err);
  fetchCallback('url', err => {
    if (err) return console.log(err);
    fetchCallback('url', err => {
      if (err) return console.log(err);
      fetchCallback('url', err => {
        if (err) return console.log(err);
        console.log('all done');
      });
    });
  });
});

As you can see, the code is more legible with Promises.

Working with Promises

We can interact with the result of the Promise by chaining together handlers, that will either wait for the Promise to be fulfilled with a value, or rejected with the first error thrown.

fetch('url')
  .then(response => console.log(response.status))
  .catch(error => console.log(error));

In the above code example fetch returns a Promise, and the Promise API allows us to chain the then and catch handlers.

Your Promise chain should include a catch handler to deal with any Promises that are rejected in the chain. To handle errors with catch is best practice.

In future versions of Node.js, unhandled Promise rejections will crash your application with a fatal exception.

A Promise is in one of these three states:

  • pending: initial state, neither fulfilled nor rejected.
  • fulfilled: the operation was completed successfully.
  • rejected: the operation failed.

Creating a Promise

A new Promise can be created by initializing one with the Promise constructor:

const myPromise = new Promise((resolve, reject) => {
  // do something asynchronous
});

The Promise constructor takes two functions as arguments, resolve and reject. We can do the asynchronous task, and then call either resolve (with the result if successful) or reject(with the error). The constructor returns a Promise object, which can then can be chained with then and catch methods.

Let's have a look at some example:

const fs = require('fs');

const myPromise = new Promise((resolve, reject) => {
  fs.readFile('example.json', (err, data) => {
    if (err) {
      reject(err);
    } else {
      resolve(data);
    }
  });
});

myPromise
  .then(data => console.log(data))
  .catch(err => console.log(err));

In the code example above, we wrapped fs.readFile in a Promise. If reading the file encountered an error, we pass it to reject, otherwise we pass the data obtained from the file to resolve. Calling resolve passes the data to our .then handler, and reject passes the error to the .catch handler.

Chaining Promises

Combining multiple Promises is one of the big advantages of Promises over using callbacks. It is difficult to orchestrate multiple callbacks together, whereas with Promises it is much more readable, and error handling is standardized between the different Promises.

Let's have a look at an example for fetching the json placeholder API to get some todos.

fetch('https://jsonplaceholder.typicode.com/todos')
  .then(response => response.json())
  .then(json => console.log(json))
  .catch(err => console.log(err));

In the example above we fetch some JSON data via an HTTP request. The fetch function returns a promise, which will either resolve or reject. The attached then handles the response by fetch, when it resolves. The response body has a json method for parsing the response from JSON to an object. The json method returns a promise of its own, which handle by attaching another then handler, and in case of error we attach a catch handler and log the error.

TL;DR

  • Promises help deal with the execution flow of asynchronous code.
  • Promises are cleaner and more maintainable than using callbacks (in most cases).
  • A Promise can have one of three different states: pending, fulfilled, or rejected.
  • We can chain then and catch methods to a Promise in order to execute code when the state changes.
  • Promises can be used to execute synchronous operations without blocking the Node.js process.

Thanks for reading and if you have any questions, use the comment function or send me a message @mariokandut.

If you want to know more about Node, have a look at these Node Tutorials.

References (and Big thanks):

HeyNode, MDN Promises, MDN - Using Promises

More node articles:

How to create a web server in Node.js

How to dynamically load ESM in CJS

How to convert a CJS module to an ESM

How to create a CJS module

How to stream to an HTTP response

How to handle binary data in Node.js?

How to use streams to ETL data?

How to connect streams with pipeline?

How to handle stream errors?

How to connect streams with pipe?

What Is a Node.js Stream?

Handling Errors in Node (asynchronous)

Handling Errors in Node.js (synchronous)

Introduction to errors in Node.js

Callback to promise-based functions

ETL: Load Data to Destination with Node.js

ETL: Transform Data with Node.js

ETL: Extract Data with Node.js

Event Emitters in Node.js

How to set up SSL locally with Node.js?

How to use async/await in Node.js

What is an API proxy?

How to make an API request in Node.js?

How does the Event Loop work in Node.js

How to wait for multiple Promises?

How to organize Node.js code

Understanding Promises in Node.js

How does the Node.js module system work?

Set up and test a .env file in Node

How to Use Environment Variables in Node

How to clean up node modules?

Restart a Node.js app automatically

How to update a Node dependency - NPM?

What are NPM scripts?

How to uninstall npm packages?

How to install npm packages?

How to create a package.json file?

What Is the Node.js ETL Pipeline?

What is data brokering in Node.js?

How to read and write JSON Files with Node.js?

What is package-lock.json?

How to install Node.js locally with nvm?

How to update Node.js?

How to check unused npm packages?

What is the Node.js fs module?

What is Semantic versioning?

The Basics of Package.json explained

How to patch an NPM dependency

What is NPM audit?

Beginner`s guide to NPM

Getting started with Node.js

Scroll to top ↑