Asynchronous Tasks with Promises in JavaScript
Introduction
This behavior used to be handled through callbacks, however, over time this became tedious to maintain and code indentation became increasingly problematic the more validations and derived processes had to be performed. This gave way to the introduction of Promises in JavaScript.
JavaScript Promises were introduced with ECMAScript 6, and brought to the language a more concise way of working with asynchronous functions.
It should be noted that Promises are not supported by Internet Explorer. To achieve this it is necessary to use polyfills.
Asynchronous functions include actions such as file reads, server requests (ajax), functions that wait a certain period of time (such as setTimeout
), processes that can be CPU intensive such as hashing information, among others.
Callbacks
Callbacks are functions sent as parameters to another function. The latter is expected to execute the received code after some time.
In the case of the setTimeout
function that we will use as an example, it receives a function as a parameter that is executed once the period defined in milliseconds ends.
let asincrono = function (cb) {
setTimeout(function () {
cb('second');
}, 3000);
};
console.log('first');
asincrono(function (result) {
console.log(result);
});
console.log('third');
The result in the console would be:
first;
third;
second; // after 3 seconds
We see that printing "second" doesn't happen until after a time (in this case the one specified in setTimeout
of 3000
ms).
Here we use a function called cb
(it can have any name), to be called once the asynchronous process has finished, in this case, a time lapse of 3 seconds.
Promise()
JavaScript Promises receive a function with two parameters (resolve
, reject
) which are functions that are called once the process executes successfully (resolve
) or fails (reject
).
Promises have three states:
- Pending (waiting to be resolved or rejected)
- Fulfilled (resolved)
- Rejected
Rewriting the previous example with promises would look like this:
// promises are instantiated with 'new'
let asincrono = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('second');
}, 3000);
});
console.log('first');
asincrono.then(function (result) {
console.log(result);
});
console.log('third');
The console output would be the same as in the previous example.
Another use of promises is being able to know when an asynchronous query, for example to a server, is completed.
let datosDeServidor = function (url) {
return new Promise(function (resolve, reject) {
let xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = function () {
resolve(xhr.responseText); // accessed with 'then()'
};
xhr.onerror = function () {
reject(xhr.statusText); // accessed with 'catch()'
};
xhr.send();
});
};
datosDeServidor(/* url */)
.then(function (response) {
// query completed
console.log(response); // xhr.responseText
})
.catch(function (error) {
// request error
console.log(error); // xhr.statusText
});
We see that then()
is used for resolve
and catch()
for reject
.
Many of the libraries used in JavaScript and NodeJs support the use of Promises. For example, axios
, used to make ajax requests is based on Promises, so its general use is:
axios({
/* options */
})
.then(function (response) {
// successful request
console.log(response);
})
.catch(function (error) {
// request failed
console.log(error);
});
Promise.all()
Promise.all()
receives an array of promises and returns an array with the promises once they have been resolved. Using as an example querying a server, we could use Promise.all()
if we have several asynchronous queries on which we want to act once they all finish.
Promise.all([
datosDeServidor(/* url from server x */),
datosDeServidor(/* url from server y */),
datosDeServidor(/* url from server z */),
])
.then(function (results) {
console.log(results[0]); // response from server x
console.log(results[1]); // response from server y
console.log(results[2]); // response from server z
})
.catch(function (error) {
console.log(error); // error from the first promise that fails
});
JavaScript Promises give us greater flexibility when handling asynchronous code. One of the most common uses of promises in web development is being able to control the flow of requests (ajax) to web servers since depending on the connection and other factors, the exact time it takes for a response is unpredictable.