Introducing Promises in JavaScript

A promise is an object used as a placeholder for a value, which might be available as a result of asynchronous operations such as a response from an HTTP request.

When we make an asynchronous call, it immediately returns a promise object to register callbacks that will run when the asynchronous call succeeds or an error occurs.

Let’s first look at an example of callbacks and then see how we can convert it into a promise.

function sayHello() {
console.log("Hello");
}
setTimeout(sayHello, 2000);

Let’s consider the above example. Here, setTimeout might be the asynchronous task that takes 2 seconds for execution and then calls the sayHello callback.

The code looks perfectly fine and runs pretty well, but there is one issue with the code. Like other asynchronous calls, we rely on setTimeout on its completion to call our function correctly and pass data while calling our function if needed, which we have given as an argument.

Most third-party libraries for asynchronous tasks have a similar pattern to accept a callback and call it properly later on. There might be two problems here:

  • IOC (Inversion of Control) — Control is with asynchronous calls, when, and how they will use our callbacks.
  • We will need to trust these libraries to call our callback correctly and don't misuse it.

Promises

Let's try to understand JavaScript promises by using promises to achieve similar functionality.

function sayHello() {
console.log("Hello");
}
const sayHelloPromise = new Promise(function(resolve, reject) {
setTimeout(resolve, 2000)
})
sayHelloPromise.then(function() {
sayHello();
})

In the above code, instead of passing sayHello to setTimeout, we have created a Promise object with new Promise, which accepts a callback function that has two arguments resolve and reject. Inside the callback function’s body, there might be an asynchronous call which, when succeeds, calls resolve method and passes resulting data into it if needed, else calls reject, if there is an error while performing the asynchronous task.

Promisify

Let’s now create a generic promisify function that converts nodejs’ callback APIs into promises, and let's use fs.readFile method from nodejs’ fs module as an example.

import fs from "fs"fs.readFile("filename", "utf8", cb);function promisify(cb) {
return (...args) => new Promise((resolve, reject) => {
cb.apply(this, [
...args,
function(err, data) {
if (err) reject(err);
resolve(data);
}
])
}
)}
const readFileAsync = promisify(fs.readFile);readFileAsync("filename", "utf8")
.then(function(data) {
console.log("Data : ", data);
})
.catch(function(err) {
console.log("Error : ", err);
});

We can break the callback API into a wrapper function that accepts a callback and returns another function. The returned function will then take arguments and will return a Promise object. And once again promise callback body can execute the asynchronous task, resolve the promise on success, and reject it on error.

Promise status

A Promise can only be in one of these states.

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

Promise Methods

Promise.prototype.then method accepts a callback, which might have a response data as an argument, to handle fulfilled state of a Promise.

Promise.prototype.catch method accepts a callback, which might have an error data as an argument, to handle rejected state of a Promise.

Promise.prototype.finally method accepts a callback, which might be called to handle post completion of a promise. The callback in finally method is called in both rejected and fulfilled states.

readFileAsync("filename", "utf8")
.then(function(data) {
console.log("Data : ", data);
})
.catch(function(err) {
console.log("Error : ", err);
})
.finally(function(someData) {
console.log("Promise Completed!");
});

Promise Chaining

Both Promise.prototype.then and Promise.prototype.catch returns another promise object, which can be chained with a new set of then and catch methods.

const promise1 = new Promise((resolve, reject) => resolve(10));promise1
.then(value => value + 10) // value = 10
.then(value => value + 10) // value = 20
.then(value => console.log(value)) // value = 30

Sr. JavaScript Developer at GlobalLogic, React, Nextjs, GraphQl, Serverless, JamStack