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
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”?
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!