While previously available via third party libraries, promises were introduced in Javascript, as a native feature, with ECMAScript6.

They provide an alternative to callbacks when dealing with asynchronous code, providing, among the other things, a cleaner way to handle errors. In this tutorial we will see how promises work, how to create them and how to use their methods.

In this tutorial you will learn:
  • What is a Javascript promise.
  • How to create a Javascript promise.
  • How promises can be used to manage asynchronous code.
  • What are the methods that can be used with a promise.

Software Requirements and Conventions Used

Software Requirements and Linux Command Line Conventions
Category Requirements, Conventions or Software Version Used
System Operating system agnostic.
Software An installation of node to follow this tutorial in a non-browser environment.
Other Knowledge of Javascript and object oriented concepts.
Conventions # - requires given linux commands to be executed with root privileges either directly as a root user or by use of sudo command
$ - requires given linux commands to be executed as a regular non-privileged user

What is a "promise"?

javascript-logo

In Javascript, a promise is an object returned as the result of an asynchronous, non blocking operation, such, for example, the one performed by the fetch builtin function. Promises were introduced as a native feature, with ECMAScript6: they represent a cleaner alternative to callbacks, thanks to features like methods chaining and the fact that they provide a way to manage errors which resembles exception handling in synchronous code. There are three states a Promised can be in:

  • Pending
  • Resolved
  • Rejected

As the name suggests, we say that a promise is pending when its result hasn't been decided yet, so it still can be resolved or rejected. We say that a promise is fulfilled when the asynchronous operation has been successful: the promise has been resolved, and it contains the result of the operation itself. Finally, a promise is said to be rejected when the asynchronous operation fails: in that case the promise will contain the reason for the failure.

Creating a Javascript Promise



As mentioned above, some functions that perform asynchronous operations, like fetch, return a promise by default, so we can use the methods and the patterns we will describe later in this tutorial out of the box. Other functions doesn't support promises yet, so we may want to create a promise around them. The constructor of a promise takes one argument, which is a callback function which itself, takes two arguments: the resolve and reject callbacks, which are called to resolve or reject the promise, respectively. Let's see a quick example on how to create a trivial promise:

const promise = new Promise(function(resolve, reject) {
  setTimeout(resolve, 100, 'success!');
});
With the code above, we created a promise, which will be actually always be resolved, because by using the setTimeout function, we call the resolve callback after a timeout of 100 milliseconds, passing the string "success!" as the sole argument of the callback. In the same way, if we wanted the promise to be rejected, we should have invoked the reject callback. Obviously a promise like the one above is not very useful to us, so we will now try to create a promise around an actually useful function.

The readFile method of the fs module, asynchronously reads the content of a file, and takes three arguments: two of them are mandatory, and one is optional. The first argument is the path of the file to be read. The second argument is optional, and with it, we can, for example, specify the encoding to be used. The third argument is a callback function, which itself takes two arguments: err and data.

If the read operation fails, the first argument will contain an Error object and the second one will be undefined; if the operation is successful, instead, the second argument will be a string representing the content of the file, or a raw buffer if no encoding is specified, while the first argument will be null. Say for example I want to read my .vimrc file using this function:

const fs = require('fs');

fs.readFile('.vimrc', 'utf-8', function (err, data) {
  if (err) {
    throw err
  }
  console.log(data)
});


First of all we required the fs module and assigned it to the fs constant, than we proceeded invoking the readFile method. In the callback accepted as the last argument of the function, we perform the needed operations depending on the result obtained. In the code above we throw an exception if some error occurs when attempting to read the file, while we just print the file content if everything goes as expected. In this case this would be the (truncated) result:

[...]
set fileformat=unix
set textwidth=79
set noswapfile
set foldmethod=indent
set foldlevel=99
set splitright
set splitbelow
set hlsearch
set incsearch
set ignorecase
set smartcase
[...]

The method we just used, readFile, performs the read operation asynchronously, so it's not blocking. By default, It doesn't, however, support promises. If we want to "promisify" the use of this method, we should create a promise around it by ourselves:

const fs = require('fs');

function readFilePromise(filepath) {
  return new Promise(function(resolve, reject) {
    fs.readFile(filepath, 'utf-8', function(err, data) {
      if (err) {
        reject(err);
      } else {
        resolve(data);
      }
    });
  });
}

Look at the code above, what have we changed? We created the readFilePromise function: inside of it a promise based on the result of the fs.readFile method is created and returned. In the previous example, we adjusted the code to throw an exception if an error in the read operation was present: in this case, instead, since we are building a promise, if an error occurs we call the reject callback, passing the error as its sole argument, this way rejecting the promise. If the read operation is performed successfully, instead, we call resolve, passing the data resulting from the read operation as the argument, thus fulfilling the promise. In the next paragraph we will see how to actually consume the promise we just created.



Promise methods

A Promise object would be of no use if we didn't have ways to interact with it and consume it. In this section we will describe the methods we can use on the promise object. Each of this methods works on a promise, and in turn, returns a promise itself, allowing us create a "stack" and perform method chaining.

The then method

The then method takes two arguments, which are actually two callbacks to be executed respectively when the promise is fulfilled and when it is rejected, and returns a promise. Sticking with the example above, here is how we could use this method to interact with the promise returned when we call the readFilePromise function:

readFilePromise('.vimrc').then(
  function onResolveCallback(data) {
    console.log(data);
  },
  function onRejectCallback(reason) {
    console.log(`The error message is ${reason}`);
  }
)

When the promise exits the pending state, and thus it's either resolved or rejected, the then method its executed. If the promise is resolved, the first callback (in this case we named the callbacks just to make easier to understand their roles) is executed, its argument holding the result of the asynchronous operation (in this case the content of the ".vimrc" file as a string). If the promise is rejected, instead, the second callback (we named it onRejectCallback) would be executed: its argument will contain the error which caused the reading operation to fail.

The catch method

Unlike then, which handles both when a promise is resolved and rejected, the catch method is more specific, and deals only with the latter case. Using this method is the equivalent of using then with undefined as the first argument, instead of the callback used to handle the case when the promise is fulfilled, and with a valid callback to handle the case when the promise is rejected, as the second one. This method returns a promise, and by using it, we can rewrite the code above this way:



readFilePromise('.vimrc')
  // Inside 'then' we manage the case when the promise is fulfilled, dealing
  // with possible errors inside 'catch'
  .then(function(data) {
    console.log(data);
  })
  .catch(function(reason) {
    console.log(`The error message is ${reason}`);
  })

Observe how we attached the catch method after then: this is possible because, as we said above, each method returns a promise itself, and so they can be chained.

The finally method

As the methods we saw above, finally returns a promise. It is always executed regardless of the state of the promise, both if it is resolved or rejected. For this reason, the callback takes no arguments, since when it runs there is no way to determine if the promise has been rejected or resolved. We use this method when we want to run generic code that should be run in any cases.

readFilePromise('.vimrc')
  .then(function (data) {
    console.log(data);
  })
  .catch(function (reason) {
    console.log(`The error message is ${reason}`);
  })
  .finally(function () {
    console.log("I am always executed!");
  })

In the example above, whether the promise is resolved or rejected, the string "I am always executed!" it's printed on the console.

The race method

This method takes an iterable (an array for example) as its argument. It returns a promise that is resolved or rejected as soon as a promise contained in the iterable, exists the pending state, and becomes either rejected or resolved. The returned promise, will have the fulfillment value or the rejection reason of said promise.



const p1 = new Promise(function(resolve, reject) {
  setTimeout(resolve, 100, 'resolved!');
});

const p2 = new Promise(function(resolve, reject) {
  setTimeout(reject, 50, 'rejected!');
});

Promise.race([p1, p2])
  .then(function (data) {
    console.log(data);
  })
  .catch(function (reason) {
    console.log(reason);
  })

In this example we created two new promises: the first one, p1, will be resolved after 100 milliseconds; the second one, p2, will be rejected after 50 milliseconds. We passed an iterable containing both promises as the sole argument of the Promise.race method. If we run the code above we obtain the following result:

rejected!

What happened? As expected the p2 promise is the first one to settle (it is rejected), consequently the promise returned by the Promise.race method, rejects with the same reason. As you can see, the state of the promise is not relevant: the first one which actually gets a status other than pending is the one that matters.

The all method

Like race, the all method takes an iterable as its sole argument. It returns a promise which will resolve once all the promises contained in the iterable will resolve (or when the iterable contains no promises) or will reject with the reason of the first promise in the iterable that will reject. For example:

const p1 = new Promise(function(resolve, reject) {
  setTimeout(resolve, 100, 'p1 resolved!');
})

const p2 = new Promise(function(resolve, reject) {
  setTimeout(resolve, 100, 'p2 resolved!');
})

Promise.all([p1, p2])
  .then(function(values) {
    console.log(values);
  })

The above code will return:

[ 'p1 resolved!', 'p2 resolved!' ]

All the Promises contained in the iterable resolved, so the pending promise returned by the all method resolved too, its value being an array containing the values of all the resolved promises. If one (and as soon as) one of the promises in the iterable rejects, the promise returned by the method rejects too, with the same reason. If the iterable passed as argument had been empty, an already resolved promise would have been returned. If the iterable contained no promises, the method would have returned an asynchronously resolved promise or an already resolved promised depending on the environment.



The resolve and reject methods

These two methods are self explanatory.

The resolve method takes an argument which is the value to be resolved by the promise. It returns a promise which is resolved with that value. The reject method, similarly, takes an argument which is the reason with the promise should be rejected with, and returns a promise which is rejected with the given reason. For example:

// Resolve a Promise
Promise.resolve('Resolved value');

// Reject a Promise
Promise.reject('Reason to reject');

Conclusions

In this tutorial we learned to know and use promises in Javascript. We saw how can we build our own promises, what are the methods associated with a promise, and how can we use it to manage asynchronous code, as a cleaner alternative to callbacks. A valid source to further increase your knowledge of promises it's the one provided by mozilla. In the next Javascript tutorial we will learn how to use arrow functions. Stay tuned on linuxconfig.org!
ARE YOU LOOKING FOR A LINUX JOB?
Submit your RESUME or create a JOB ALERT on LinuxCareers.com job portal.
DO YOU NEED ADDITIONAL HELP?
Get extra help by visiting our LINUX FORUM or simply use comments below.

You may also be interested in:



Comments and Discussions