Promises 101
Translation of the first part of an excellent article on promises. Basic techniques for creating and managing promises.
Promises are used for operations that take an indefinite time to calculate. An example of such an operation is a network request when we request data from the API and cannot determine exactly when the response will be received.
If there are other operations the execution of which depends on this network request, then a problem looms. Without promises, we will have to use a string of callbacks to build a sequence of operations. This is normal if we have one asynchronous action. But if you need to take several consecutive asynchronous steps, the callbacks become uncontrollable and the result is notorious as callback hell
doSomething(function(responseOne) {
doSomethingElse(responseOne, function(responseTwo, err) {
if (err) { handleError(err); }
doMoreStuff(responseTwo, function(responseThree, err) {
if (err) { handleAnotherError(err); }
doFinalThing(responseThree, function(err) {
if (err) { handleAnotherError(err); }
// Выполнено
}); // конец doFinalThing
}); // конец doMoreStuff
}); // конец doSomethingElse
}); // конец doSomething
Promises provide a standardized and understandable method for solving tasks that must be performed sequentially.
doSomething()
.then(doSomethingElse)
.catch(handleError)
.then(doMoreStuff)
.then(doFinalThing)
.catch(handleAnotherError)
Create Promises
Promises are created using the Promises Designer. It is a function with two arguments ( resolve
& reject
) as parameters.
var promise = new Promise(function(resolve, reject) { /* Содержимое промиса */ } )
Inside this function, we can perform any asynchronous tasks. To mark a promise as fulfilled , we call resolve()
, passing it the value that we want to return. To mark a promise as rejected or unsuccessful, we call by reject()
passing it an error message. Before the promise becomes fulfilled or rejected, it is in a waiting state .
Here is the promis version of XMLHttpRequest -
/* CREDIT - Jake Archibald, http://www.html5rocks.com/en/tutorials/es6/promises/ */
function get(url) {
return new Promise(function(resolve, reject) {
var req = new XMLHttpRequest();
req.open('GET', url);
req.onload = function() {
if (req.status == 200) {
resolve(req.response); /* ПРОМИС ВЫПОЛНЕН */
} else {
reject(Error(req.statusText)); /* ПРОМИС ОТКЛОНЁН */
}
};
req.onerror = function() { reject(Error("Network Error")); };
req.send();
});
}
Using Promises
To execute a promise, we can call it like any ordinary function. But, since this is a promise, we have access to a .then
method that we can add to the function and which will be executed when the promise leaves the standby mode.
.then()
The method takes two optional parameters. The first is a function that is called when the promise is resolved. The second is a function that is executed if the promise is rejected.
get(url)
.then(function(response) {
/* successFunction */
}, function(err) {
/* errorFunction */
})
Error processing
Since both parameters (successFunction and errorFunction) are optional, we can split them into two .then()
for better readability.
get(url)
.then(function(response) {
/* successFunction */
}, undefined)
.then(undefined, function(err) {
/* errorFunction */
})
To make the code even more understandable, we can use a .catch()
method that is a shorthand for.then(undefined, errorFunction)
get(url)
.then(function(response) {
/* successFunction */
})
.catch(function(err) {
/* errorFunction */
})
Chaining
The real value of promises is that we can perform several asynchronous functions in order. We can combine .then()
and .catch()
together to create a sequence of asynchronous functions.
We can do this by returning another promise after fulfilling or rejecting the previous one. For instance -
get(url)
.then(function(response) {
response = JSON.parse(response);
var secondURL = response.data.url
return get( secondURL ); /* Возвращаем новый промис */
})
.then(function(response) {
response = JSON.parse(response);
var thirdURL = response.data.url
return get( thirdURL ); /* Возвращаем новый промис */
})
.catch(function(err) {
handleError(err);
});
If the promise is resolved, the closest .then()
in the sequence is called. If the promise is rejected, then the closest .catch()
in the sequence.
Parallel Promises
A situation may arise when we need to perform several promises in parallel, and continue the algorithm only after all the promises are completed. For example, if we want to get a series of images and only after that display them on the page.
To do this, we need to use two methods. This Array.map()
, in order to apply a promise to each element of the array and save the result in a new array. And Promise.all()
, which will execute resolve()
if all the promises in the array are executed. If at least one promise in the array is rejected, Promise.all()
it will also be rejected.
var arrayOfURLs = ['one.json', 'two.json', 'three.json', 'four.json'];
var arrayOfPromises = arrayOfURLs.map(get);
Promise.all(arrayOfPromises)
.then(function(arrayOfResults) {
/* Сделать что-нибудь, когда все промисы в массиве зарезолвятся */
})
.catch(function(err) {
/* Выполняется, если хоть один промис в массиве отклонён */
})
If we look at the Network panel of the Development tools, we will see that all requests happen in parallel.
If you need support for IE and / or Opera Mini, use polyfil .
Thanks for attention!