Promise in JavaScript and how to use util.promisify in Node.js

Cover Image for Promise in JavaScript and how to use util.promisify in Node.js
Ali Bentaleb

Promises were introduced in JavaScript since ES6 ( ECMAScript2015), and their purpose is to execute a deferred or asynchronous computation, i.e. a task that you would run and it will continue working in the background, once it is finishd, the results are returned. On the other hand, promises can replace the need of mutiple callbacks in functions through promisifying and so avoid what is called _callback hell_.

A Promise also acts as a proxy for a result that is initially unknown.

Promise object states

A promise can be one of three mutually exclusive states:

  • fulfilled: A promise P is fulfilled if when calling the then method of the P promise, it will immediately enqueue a Job to call the function fulfill as defined in P.then(fulfill).

In Node.js this can be implemented as below:

//this promise is fulfilled as per its definition
const myPromise = new Promise((resolve) => resolve());
//the function is called immediately since myPromise is resolved
const fulfill = () => console.log('Execute resolve function');
myPromise.then(fulfill);
  • rejected: A promise P is rejected if when calling the then method of the P promise, it will immediately enqueue a Job to call the function reject in P.then(fulfill,reject).
//this promise is rejected as per its definition
const myPromise = new Promise((resolve, reject) => reject());
//the fulfill function
const fulfill = () => console.log('Promise is OK');

//the reject function
const reject = () => console.log('Promise is rejected');
//the then method is taking the fulfill and the reject as arguments, the reject is executed immediately since the myPromise is rejected
myPromise.then(fulfill, reject);
  • pending: A promise P is pending if it is neither fulfilled nor rejected.

There are also some naming conventions by ECMAScript for Promises based on their states, we can site:

  • settled: the promise state is either rejected or fulfilled
  • resolved: the promise is settled or matching the state of another promise.

Promise prototype object methods

Promise constructor

As defined by The ECMAScript, the promise constructor takes in a function object with two arguments.

Let’s say the promise arguments are resolve and reject, these functions will take only up to one argument when called, in the sample below, the resolve and reject functions takes in a String as an argument.

const a = true;
const p = new Promise((resolve, reject) => {
	if (a) resolve('Resolved');
	else reject('reject');
});

It is also possible to call the Promise constructor with one argument only

const p = new Promise((resolve) => setTimeout(resolve, 5000, 'first'));
p.then((result) => console.log(result));
The above is a valid promise definition and Node.js does not complain about it.

Promise then method

The then method can be called with two arguments, onFulfilled and onRejected functions respectively.

It returns a promise also, which means it can be chained with other promises.

Promise catch method

The catch method is equivalent to the then method when called with undefined and onReject function.

It returns also a promise, so the chaining is possible too.

Let’s see how we can write both catch and its equivalent with then(undefined,onReject)

const myPromise = new Promise((resolve, reject) => reject());
const fulfill = () => console.log('Promise is OK');
const reject = () => console.log('Promise is rejected');
//the catch method is called immediately since promise is rejected
myPromise.then(fulfill).catch(reject); //output: Promise is rejected
//catch equivalent with then
myPromise.then(fulfill).then(undefined, reject);

Promise finally method

The finally method when called returns another promise, and it is resolved when the original promise is resolved, it is an optional method, we can use then instead.

Chaining promises

A promise can return another promise, which leads to create promises chain.

For instance, then, catch and finally methods of the Promise object return promises, so when concatenated together we can create promises chain.

A very known sample about chain promises is the use of the fetch function in node-fetch package which is very commonly used in Node.js development, more than 28 million downloads per week for this package in npmjs, so you can figure out how popular it is. Let’s try it out

Create a project using npm init, and add “type”:“module” to your package.json

import fetch from 'node-fetch';

//we are using typicode free mock REST API
fetch('https://jsonplaceholder.typicode.com/todos/2')
	//fetch as defined in node-fetch package returns a promise, so it is possible to use the then method
	//response is instance of node-fetch/Response object which has a method called json()
	// json method is defined as async which is an evolved way to return promises in JavaScript
	.then((response) => response.json())
	//the use of then is possible since json() method is async() which means it returns a promise
	//the result are printed then to the screen
	.then((toto) => console.log(toto));
//the output results
/*
  {
  userId: 1,
  id: 2,
  title: 'quis ut nam facilis et officia qui',
  completed: false
}
*/

The fetch function of node-fetch package returns a promise which let us call the then method.

the async keyword is prepended to the json() method of Body.js.

The async keyword when prepended to a function means that it will return a promise, so we are able to use another then and so demonstrate the chaining promises.

Promise properties

Promise.all()

The all function takes in an array of promises, it is fulfilled once all promises are fulfilled or rejected if one of the promises is rejected, the return value is either all fulfilled promises in an array shape, or only the first rejected promise result if one of the promises is rejected.

Let’s see how it can be used with Node.js

//f3 and f4 are resolved by definition
const f3 = new Promise((resolve) => resolve('f3 is resolved'));
const f4 = new Promise((resolve) => resolve('f4 is resolved'));
//output : [ 'f3 is resolved', 'f4 is resolved' ]
Promise.all([f1, f2]).then((x) => console.log(x));

//here f1 is resolved, f2 is rejected
const f1 = new Promise((resolve) => resolve('f1 is resolved'));
const f2 = new Promise((resolve, reject) => reject('f2 is rejected'));
//output: f2 is rejected
Promise.all([f1, f2])
	.then((x) => console.log(x))
	.then(undefined, (x) => console.log(x));
//output: f2 is rejected
Promise.all([f1, f2])
	.then((x) => console.log(x))
	.catch((x) => console.log(x));

If all the promises are resolved, it returns the result of all promises in a an array shape, if one of them is rejected, it returns the first rejected promise, in the sample above we have catch the error with the catch method and its equivalent then(undefined,f) which outputs the same results.

promise.allSettled

The allSettled function returns a promise that is fulfilled with an array of promise whether they are rejected or fulfilled.

const f1 = new Promise((resolve) => resolve('f1 is resolved'));
const f2 = new Promise((resolve, reject) => reject('f2 is rejected'));
Promise.allSettled([f1, f2])
	.then((x) => console.log(x))
	.catch((x) => console.log(x));
/* output
[
  { status: 'fulfilled', value: 'f1 is resolved' },
  { status: 'rejected', reason: 'f2 is rejected' }
]
*/

Promise.race

The race function returns a new promise, returns the first settled promise and ignore the rest.

const f1 = new Promise((resolve) => {
	setTimeout(resolve, 5000, 'first');
});
const f2 = new Promise((resolve) => {
	setTimeout(resolve, 7000, 'second');
});
Promise.race([f1, f2])
	.then((x) => console.log(x))
	.catch((x) => console.log(x));
/* output
first
*/

Promise.Any

The any function returns a promise that is fulfilled by the first given promise to be fulfilled or rejected with the aggregated error if all promises are rejected.

promise.reject

The reject function returns a new promise rejected with the passed argument.

promise.resolve

The resolve function returns a new promise resolved with the passed argument.

Please note that promise.any, promise.reject and promise.resolve functions are not supported in Node.js version 14. You need to upgrade to a later version to use these functions.

Promisifying in Node.js

The promisifying is a technique to wrap a function that takes in a callback in its arguments with promises, so the function would return a promise instead, and thus to avoid callback hell. let’s see this example where we replace writeFile function that takes in a callback and make it return a promise in a beautiful manner

const fs = require('fs');
// we wrap the writeFile callback with promises
//if write content is successful we got the message successfully written
//otherwise the catch returns the error
const writeContent = (filename, content) =>
	new Promise((resolve, reject) => {
		fs.writeFile(filename, content, (err) => {
			if (err) reject(err);
			else resolve('file successfully written');
		});
	});

writeContent('/test.txt', 'hi Ali')
	.then((x) => console.log(x))
	.catch((x) => console.log(x));

So the writeContent function would return a text in a file and return a promise, this promise will be fulfilled if everything is ok and show us the OK message, otherwise it will print out the error in the catch part.

util.promisify in Node.js

util.promisify is a function added on Node.js since V8 and its goal to simplify the promisifying technique especially with functions that takes in a callback with the form of (err, value) => …

Let’s see how we can do the same example above but this time with promisify function.

const fs = require('fs');
const util = require('util');

const writeFilePromise = util.promisify(fs.writeFile);
const writeContent = (filename, content) =>
	writeFilePromise(filename, content)
		.then((x) => console.log('file successfully written'))
		.catch((err) => console.log(err));

writeContent('/test.txt', 'hi Ali');

So the util.promisify let us simplify code and use the then and catch promises instantly so to replace functions using callbacks of the form (err, value) => ….

References

ECMA-262 specifications

Promises in Node.js

module.exports and ES modules ...The three dots in javascript -...


More interesting articles