📜 ⬆️ ⬇️

Promise JavaScript Guide for Newbies

image

We have prepared this material for JavaScript programmers who are just starting to deal with “Promise”. Promises in JavaScript is a new tool for working with deferred or asynchronous computing, added to ECMAScript 2015 (6th version of ECMA-262).

Before the appearance of “promises”, asynchronous tasks could be solved with the help of callback functions or with the help of event handling. A universal approach to solving asynchronous tasks is event handling. A less convenient, but also valid, way to use callback functions. Of course, the choice of solution depends on the task before you. The solution to problems with the help of "promises", rather, is intended to replace the approach to callback functions.

There is a significant drawback to using callback functions in terms of code organization: " callback hell ". This disadvantage is that there is a parameter in the callback function, which, in turn, is also a callback function - and this can continue indefinitely.
')


There may be several levels of such nestings. This leads to poor code reading and confusion between calls to callback functions. This, in turn, will lead to errors. With such a code structure, it is very difficult to find errors.
If you still use this approach, it will be more efficient to initialize the callback functions separately, creating them in the right place.

Let's look at the work of "promises" on the example of a specific task:
After loading the browser page, you need to display images from the specified list .

The list is an array in which the path to the image. For example, to display images in the slider of your banner system on the site or asynchronously download images in the photo album.

/** *   * (,   1.jpg, 2.jpg, 3.jpg, 4.jpg ,  * fake.jpg - ) * * @type {string[]} */ var imgList = ["img/1.jpg", "img/2.jpg", "img/fake.jpg", "img/3.jpg", "img/4.jpg"]; 

First, we write a function that loads one image at the specified url.

function loadImage (url)
 /** * *     url * * @param url * @returns {Promise} */ function loadImage(url) { // "" return new Promise(function(resolve, reject) { var img = new Image(); img.onload = function() { //    ,  ""  url   return resolve(url); } img.onerror = function() { //     ,  ""  url   return reject(url); } img.src = url; }); } 


The “promise” object is created using the new Promise (...) constructor, to which an anonymous function with two parameters is passed as an argument: resolve, reject. They, in turn, are also functions. Resolve () - reports that the code is executed "successfully", reject () - the code is executed with "error" (what is considered an "error" when executing your code, you decide. This is something like if (true) {. ..} else {...}).

The Promise interface (promise) is a wrapper for a value unknown at the time the promise is created. It allows processing the results of asynchronous operations as if they were synchronous: instead of the final result of the asynchronous method, a promise is returned, the result of which can be obtained at some point in the future.

When creating, the promise is pending (pending state), and then it can become fulfilled, returning the result (value), or rejected, returning the reason for the refusal.

You can pass any objects to the resolve () and reject () methods. In the reject () method, as a rule, an object of the Error type is passed indicating the cause of the error (the “rejected” state of the “promise”). In any case, it is not necessary. The decision how further you will handle such situations is yours.

At the moment it may seem that the "promise" absolutely does not need to be used in this situation. So far we are only setting up some kind of indicator of whether the image has been loaded. However, soon you will see that this mechanism can easily, intuitively understand what happens after the task is completed (the image is loaded or not).

Then () and catch () methods


Whenever you create a promise object, two methods become available: then () and catch () . Using them, you can execute the necessary code with the successful resolution of the "promise" (resolve (...)) or the code that handles the situation with the "error" (reject (...)).

then () and catch ()
 function myPromise() { return new Promise(function(resolve, reject) { //   var ascync = true; // false if (!ascync) return reject(new Error("  ...")); return resolve(1); }); } myPromise() .then(function(res) { console.log(res); // 1 }) .catch(function(err){ console.log(err.message); //  "  ..." }); 

Note: it is not necessary to return (return) resolve (...) or reject (...) :. In the example above, one would write:

 //   var ascync = true; // false if (!ascync) { reject(new Error("  ...")); } else { resolve(1); } 


As a result of the myPromise () call, the then () or catch () method would still work. The best thing to do right away is to always return resolve (...) or reject (...). In the future, this will help to avoid a situation where the code will not work as expected.

Two anonymous functions are passed to the then () and catch () methods. The syntax of the then () method is generally as follows:

 then(function onSuccess(){}, function onFail(){}); 

The function onSuccess () {} parameter will be called in case of successful execution of the “promise”, function onFail () {} - in case of an error. For this reason, the following code will work the same way:

Examples of the then () method
 myPromise() .then(function(res) { console.log(res); // 1 }) .catch(function(err){ console.log(err.message); //  "  ..." }); myPromise() .then(function(res) { console.log(res); // 1 }, function(error) { console.log(err.message); //  "  ..." }); myPromise() .then(function(res) { console.log(res); // 1 }) .then(undefined, function(error) { console.log(err.message); //  "  ..." }); 


It is much more familiar and clearer to use catch (...). You can also call the catch () method “in the middle” of the then () call chain if the logic of your code requires: then (). Catch (). Then (). Don't forget to call catch () last in the chain: this will allow you to always catch "Erroneous" situations.

Call our loadImage (url) method and, for example, add one image to the page:

 //,       id="images", , div loadImage(imgList[0]) .then(function(url) { $('#images').append('<img src="'+url+'" style="width: 200px;" />'); }) .catch(function(url) { //   ,  ,      Error //,          ,   ,   -   ... console.log("      : ", url); }); 

Sequential recursive loading and display of images


Let's write a function for sequential display of images:

function displayImages (images)
 /** *       * * @param images -   url */ function displayImages(images) { var imgSrc = images.shift(); //      if (!imgSrc) return; //        //     ,   return loadImage(imgSrc) .then(function(url) { $('#images').append('<img src="'+url+'" style="width: 200px;"/>'); return displayImages(images); // }) .catch(function(url) { // -    ,     console.log('      : ', url); return displayImages(images); // }); } 


The displayImages (images) function sequentially traverses the array with the url of the images. In case of a successful upload, we add an image to the page and proceed to the next url in the list. In the opposite case - just go to the next url in the list.

Perhaps this image display behavior is not exactly what is needed in this case. If you want to show the entire image only after they have been loaded, you need to implement the work with an array of "promises".

 var promiseImgs = []; promiseImgs = imgList.map(loadImage); //  promiseImgs = imgList.map(function(url){ return loadImage(url); }); 

The promiseImgs array now contains “promises”, in which the state can be either “allowed” or “rejected”, since the image fake.jpg does not physically exist.

To complete the task, one could use the Promise.all (...) method.
Promise.all (iterable) returns a promise that will be fulfilled after all promises are fulfilled in the passed iterated argument.

However, we have an image in the list that does not physically exist. Therefore, the Promise.all method cannot be used: we need to check the state of the object “promise” (resolved | rejected).

If there is at least one in the array of “promises” that is “rejected”, then the Promise.all method will also return a “promise” with this state, without waiting for the passage through the entire array.

Therefore we will write the loadAndDisplayImages function.

We load images, and show them on the page all at once


function loadAndDisplayImages
 /** * * @param imgList -  url * @returns {Promise} */ function loadAndDisplayImages(imgList) { var notLoaded = [];// url,     var loaded = [];// url,    var promiseImgs = imgList.map(loadImage); //    reduce(...) -  Promise,         : //loadAndDisplayImages(...).then(...).catch(...); return promiseImgs.reduce(function (previousPromise, currentPromise) { return previousPromise .then(function() { //   ,   previousPromise -   resolved (= Promise.resolve()) return currentPromise; }) .then(function(url) // ""   resolved { $('#images').append('<img src="'+url+'" style="width: 200px;"/>'); loaded.push(url); return Promise.resolve(url); }) .catch(function(url)// ""   rejected { console.log('      : ', url); notLoaded.push(url); return Promise.resolve(url); }); }, Promise.resolve()) .then(function (lastUrl) { console.log('lastUrl:', lastUrl); let res = {loaded: loaded, notLoaded: notLoaded}; //   Promise,     return Promise.resolve(res); }); } loadAndDisplayImages(imgList) .then(function(loadRes) { console.log(loadRes); }) .catch(function(err) { console.log(err); }); 


You can view network activity in the browser and make sure of parallel operation (for clarity, Chrome has Wi-Fi connection emulation enabled (2ms, 30Mb / s, 15M / s):



Having figured out how to work with Promise, it will be easier for you to understand the principles of operation, for example, with the Yandex.Maps API, or the Service Worker - this is where they are used.

UPD: The article did not mention one important point, which, in part, was related to the advice to write return resolve () or return reject ().

When these methods are called, the “promise” is set to its final state “fulfilled” or “rejected”, respectively. After this state can not be changed. Examples can be found in the comments .

Source: https://habr.com/ru/post/317256/


All Articles