📜 ⬆️ ⬇️

Use of promises in javascript

Periodically, we publish materials that somehow relate to the use of promises in JavaScript.


Why is there so much attention to promises? We believe that the whole thing is that this technology is very much in demand, and that it is rather difficult to understand.

Therefore, if you want to better understand the promises, we offer you a translation of the next article on this topic. Its author says that he has been developing in Java and PHP for the last 10 years, but all this time he has been looking at JavaScript with interest. Recently, he decided to get serious about JS and the first interest in his topic were promises.
')


We believe that this material will be interesting to novice developers who feel that, although they use promises, they are not well understood yet. It is quite possible that the story of someone who looks at JavaScript with a fresh look and seeks to explain to others what he understood himself, apart from the fact that some things are clear to everyone and without explanation, will help beginners in mastering the mechanisms of JavaScript.

Javascript newbie eyes


Anyone who begins to write in JavaScript, can feel what is called, "not at ease". Some say that JS is a synchronous programming language, others argue that it is asynchronous. A novice hears about the code that blocks the main thread and the code that does not block it, about design patterns based on events, about the life cycle of events, about the stack of function calls, about the event queue and their surfacing, about polyfills. He learns that there are such things as Babel, Angular, React, Vue and myriads of other libraries. If you have just learned in such a “newcomer” yourself - you should not worry about it. You are not the first and not the last. There is even a term for this - the so-called "JavaScript-fatigue" (JavaScript fatigue). On this occasion , Lucas F Costa aptly said : “ JavaScript-fatigue is something that can be observed when people use tools that they don’t need to solve problems that they don’t have .”

But let's not talk about sad things. So, JavaScript is a synchronous programming language that, thanks to the mechanism of callbacks, allows you to call functions in the same way as in asynchronous languages.

A simple story about promises


The word "promise" is translated as "promise." Promise objects in programming, which we call “promises,” are very similar to the usual promises that people make to each other in real life. So let's first talk about such promises.

In Wikipedia, you can find the following definition of the word "promise": "the obligation, the consent of someone to do something, or, conversely, not to do." In Ozhegov's dictionary, a “promise” is a “voluntary commitment to do something.”

So what do we know about promises?

  1. A promise gives you a guarantee that something will be done. It does not matter who exactly does it: the one who made the promise, or someone else, at the request of the one who made the promise. A promise gives confidence in something, based on that confidence, the one who received the promise may, for example, make some plans.
  2. A promise can be either fulfilled or not.
  3. If the promise is fulfilled, then you, as a result, expect something that you will be able to use in the future to carry out any actions or implement some plans.
  4. If the promise is unfulfilled, then you will want to know why the one who made it could not fulfill it. Once you know the cause of the incident and you will have confidence that the promise is not fulfilled, you can think about what to do next, or how to cope with the situation.
  5. After you have been promised something, all you have is some kind of guarantee. You cannot take advantage of what you have been promised immediately. You can determine for yourself what you need to do if the promise is fulfilled (therefore, you get the promise), and what you need to do if it is broken (in this case, you know the reason for what happened, and therefore you can think of a backup plan of action ).
  6. There is a possibility that the person who made the promise will simply disappear. In such cases, it is useful that the promise be tied to some time frame. For example, if the one who gave you the promise does not appear in 10 days, we can assume that he has some problems and he has broken the promise. As a result, even if the one who made the promise fulfills it after 15 days, it will not matter, as you are already acting on an alternative plan, without relying on the promise.

Now go to javascript.

Javascript promises


I have one rule: doing JavaScript, I always read the documentation , which can be found on MDN. It seems to me that this resource favorably differs from the others with its specificity and clarity of presentation. Therefore, studying the promises, I got acquainted with the relevant material and experimented with the code in order to get used to the new syntactic constructions.

In order to understand the promises, you need to deal with two main things. The first is the creation of promises. The second is the processing of the results returned by promises. Although most of the code we write is aimed at working with promises created, for example, by some libraries, a complete understanding of the mechanisms of work of promises will no doubt be useful. In addition, for a programmer who already has some experience, knowing how to create promises is just as important as knowing how to work with them.

Creating promises


This is how promises are created:

new Promise( /* executor */ function(resolve, reject) { ... } ); 

The constructor takes a function that performs certain actions, we called it executor here. This function takes two parameters, resolve and reject , which, in turn, are also functions. Promises are usually used to perform asynchronous operations or code that can block the main thread, for example, one that works with files, makes certain API calls, queries database, deals with input / output operations, and so on. Running such asynchronous operations is performed in the executor function. If the asynchronous operation completes successfully, then the result expected from promise will be returned by calling the function resolve . The situation in which this function is called is determined by the creator of the promise. Similarly, when an error occurs, information about what happened is returned by calling the reject function.

Now that we know in general how to create promises, we will create a simple promise in order to better understand everything.

 var keepsHisWord; keepsHisWord = true; promise1 = new Promise(function(resolve, reject) { if (keepsHisWord) {   resolve("The man likes to keep his word"); } else {   reject("The man doesnt want to keep his word"); } }); console.log(promise1); 

This is what this code will output:


Promis has state (PromiseStatus) and value (PromiseValue)

Since our promise is instantly resolved, we cannot explore its initial state. So let's create a new promise that needs some time to resolve. The easiest way to do this is by resorting to the setTimeout function.

 promise2 = new Promise(function(resolve, reject) { setTimeout(function() {   resolve({     message: "The man likes to keep his word",     code: "aManKeepsHisWord"   }); }, 10 * 1000); }); console.log(promise2); 

In this code, a promise is created, which will unconditionally be resolved in 10 seconds. This gives us the opportunity to look at the state of unresolved promise.


Unresolved Promise

After 10 seconds, the promise will be resolved. As a result, both PromiseStatus and PromiseValue will be updated accordingly. As you can see, in this example we have changed the function that is called upon successful resolution of the promise, now it returns not an ordinary string, but an object. This is done in order to demonstrate the ability to return complex functions of data structures using the function resolve .


Promise allowed after 10 seconds and returning an object

Let us now look at the promis, which we decided not to allow, but to reject. To do this, modify the code that was already used in the first example.

 keepsHisWord = false; promise3 = new Promise(function(resolve, reject) { if (keepsHisWord) {   resolve("The man likes to keep his word"); } else {   reject("The man doesn't want to keep his word"); } }); console.log(promise3); 

Since we do not handle the situation of discarding promises, an error message will be displayed in the browser console (Google Chrome is used here). We will talk more about this below.


Rejected promis

Now, after analyzing all three examples, we can see that three different values ​​can appear in PromiseStatus : pending (waiting), resolved (successful resolution) and rejected (deviation). When the promise is created, the value of pending will be in PromiseStatus , and in PromiseValue will be undefined . These values ​​will be maintained until permission or rejection of promis. When the promise is resolved or rejected , it is called the settled promise. Such a promise has moved from the waiting state to the state in which it has either a resolved state or a rejected state.

Now, after we learned how to create promises, we can talk about how to handle what they return. In order to understand this, we need to understand the structure of the object Promise .

Promise object


According to the MDN documentation, a Promise object is the result of the successful or unsuccessful completion of an asynchronous operation.

The Promise object has static methods and object prototype methods. Static methods can be called without creating an object instance, and to call prototype methods you need an instance of a Promise object. Note that both static and normal methods return Promise objects. It simplifies the work.

PromPromise Prototype Methods


We first talk about the methods of the prototype of the object Promise . There are three such methods. Do not forget that these methods can be called on an instance of a Promise object, and that they return promises. Thanks to all these methods, you can designate handlers that respond to changes in the state of promises. As we have seen, when a promise is created, it is in a pending state. When promise goes into the resolved or rejected state, at least one of the following methods will be invoked:

 Promise.prototype.catch(onRejected) Promise.prototype.then(onFulfilled, onRejected) Promise.prototype.finally(onFinally) 

Below is a diagram of the operation of the promise and events leading to the .catch .then and .catch . Since these methods return Promise objects, their calls can be chained, this is also reflected in the diagram. If the promis provides for the use of the .finally method, it will be called when the promise becomes settled , regardless of whether the promise was successfully resolved or rejected.


Promise work pattern (image taken from here )

Here is a little story. You are a schoolboy and ask your mother to buy you a mobile phone. She says: "If our savings are more than the cost of the phone, I'll buy it for you." Now retell this story in JavaScript.

 var momsPromise = new Promise(function(resolve, reject) { momsSavings = 20000; priceOfPhone = 60000; if (momsSavings > priceOfPhone) {   resolve({     brand: "iphone",     model: "6s"   }); } else {   reject("We donot have enough savings. Let us save some more money."); } }); momsPromise.then(function(value) { console.log("Hurray I got this phone as a gift ", JSON.stringify(value)); }); momsPromise.catch(function(reason) { console.log("Mom coudn't buy me the phone because ", reason); }); momsPromise.finally(function() { console.log(   "Irrespecitve of whether my mom can buy me a phone or not, I still love her" ); }); 

This is what this code will output:


Mom did not keep a promise

If we change the value of the momsSavings variable to 200,000, then mom can buy a gift for her son. In this case, the above code will output the following.


Mom kept her promise

Now let's imagine that the code in question is designed as a library, and we are using this library. Let's talk about the effective use of the methods .then and .catch .

Since the .then method can be assigned both the onFulfilled handler, called when the promise is successfully resolved, and the onRejected handler, called when the promise is rejected, instead of using both the .then method and the .catch method, we can achieve the same effect with only one method .then . Here is what it might look like:

 momsPromise.then( function(value) {   console.log("Hurray I got this phone as a gift ", JSON.stringify(value)); }, function(reason) {   console.log("Mom coudn't buy me the phone because ", reason); } ); 

This is a working example, but in order not to suffer from readability of the code, it is better, instead of one universal .then , to use the methods .then and .catch .

In order for these examples to be run in the browser, and specifically, in Google Chrome, I tried to avoid external dependencies. In order to better understand what we will look at next, let's create a function that returns a promise, the resolution or rejection of which happens randomly. This will allow us to try out different scenarios for working with promises. In order to understand the peculiarities of the operation of asynchronous functions, we will set random delays in our promises. Since we need random numbers, let's create a function that returns a random number between x and y . This is the function.

 function getRandomNumber(start = 1, end = 10) { //,      start  end >=1  end > start return parseInt(Math.random() * end) % (end-start+1) + start; } 

Now create a function that returns the promises. promiseTRRARNOSG call it promiseTRRARNOSG . The name of this function is decoded as promiseThatResolvesRandomlyAfterRandomNumnberOfSecondsGenerator , that is, it is a generator of promises that are randomly allowed or rejected in a random number of seconds. This function will create a promise that will be allowed or rejected after a random time interval between 2 and 10 seconds. In order to randomly resolve or reject the promise, we get a random number between 1 and 10. If this number is greater than 5, the promise will be allowed, if not, it is rejected.

 function getRandomNumber(start = 1, end = 10) { //,      start  end >=1  end > start return (parseInt(Math.random() * end) % (end - start + 1)) + start; } var promiseTRRARNOSG = (promiseThatResolvesRandomlyAfterRandomNumnberOfSecondsGenerator = function() { return new Promise(function(resolve, reject) {   let randomNumberOfSeconds = getRandomNumber(2, 10);   setTimeout(function() {     let randomiseResolving = getRandomNumber(1, 10);     if (randomiseResolving > 5) {       resolve({         randomNumberOfSeconds: randomNumberOfSeconds,         randomiseResolving: randomiseResolving       });     } else {       reject({         randomNumberOfSeconds: randomNumberOfSeconds,         randomiseResolving: randomiseResolving       });     }   }, randomNumberOfSeconds * 1000); }); }); var testProimse = promiseTRRARNOSG(); testProimse.then(function(value) { console.log("Value when promise is resolved : ", value); }); testProimse.catch(function(reason) { console.log("Reason when promise is rejected : ", reason); }); //             ,    .     ,  - . for (i=1; i<=10; i++) { let promise = promiseTRRARNOSG(); promise.then(function(value) {   console.log("Value when promise is resolved : ", value); }); promise.catch(function(reason) {   console.log("Reason when promise is rejected : ", reason); }); } 

Run this code in the browser console to see how the allowed and rejected promises behave. Next, we will talk about how you can create a lot of promises and check the results of their implementation using other mechanisms.

â–ŤStatic methods of the Promise object


There are four static methods of the Promise object.

Here are two methods - Promise.reject(reason) and Promise.resolve(value) , which allow you to create, respectively, rejected and allowed promises.

Here's how to work with the Promise.reject method, creating rejected promises.

 var promise3 = Promise.reject("Not interested"); promise3.then(function(value){ console.log("This will not run as it is a rejected promise. The resolved value is ", value); }); promise3.catch(function(reason){ console.log("This run as it is a rejected promise. The reason is ", reason); }); 

Here is an example of using the Promise.resolve method, which creates successfully resolved promises.

 var promise4 = Promise.resolve(1); promise4.then(function(value){ console.log("This will run as it is a resovled promise. The resolved value is ", value); }); promise4.catch(function(reason){ console.log("This will not run as it is a resolved promise", reason); }); 

It should be noted that the promise can have several handlers. For example, based on the previous example, you can get the code shown below.

 var promise4 = Promise.resolve(1); promise4.then(function(value){ console.log("This will run as it is a resovled promise. The resolved value is ", value); }); promise4.then(function(value){ console.log("This will also run as multiple handlers can be added. Printing twice the resolved value which is ", value * 2); }); promise4.catch(function(reason){ console.log("This will not run as it is a resolved promise", reason); }); 

This is what it will bring to the browser console:


Using multiple .then when working with promis

The following two methods, Promise.all and Promise.race , are designed to work with promise sets. If, to solve a problem, you need to process several promises, it is most convenient to put these promises in an array and then perform the necessary actions with them. In order to understand the essence of the methods considered here, we will not be able to use our convenient function promiseTRRARNOSG , since the result of its work depends too much on the will of the case. It will be more convenient for us to take advantage of something that produces more predictable promises, which will allow us to understand their behavior. Therefore, we will create two new functions. One of them ( promiseTRSANSG ) will create promises that are resolved after n seconds, the second ( promiseTRSANSG ) will create promises that are rejected after n seconds.

 var promiseTRSANSG = (promiseThatResolvesAfterNSecondsGenerator = function( n = 0 ) { return new Promise(function(resolve, reject) {   setTimeout(function() {     resolve({       resolvedAfterNSeconds: n     });   }, n * 1000); }); }); var promiseTRJANSG = (promiseThatRejectsAfterNSecondsGenerator = function( n = 0 ) { return new Promise(function(resolve, reject) {   setTimeout(function() {     reject({       rejectedAfterNSeconds: n     });   }, n * 1000); }); }); 

Now we will use these functions in order to understand the features of the Promise.all method.

Prom Method Promise.all


From the MDN documentation, you can see that the Promise.all(iterable) method returns a promise that will be resolved when all promises that are passed as an iterable argument are iterable , or when this argument does not contain promises. This promise will be rejected if any of the transmitted promises are rejected.
Consider a few examples.

Example â„–1


All promises will be allowed here. This scenario is most common.

 console.time("Promise.All"); var promisesArray = []; promisesArray.push(promiseTRSANSG(1)); promisesArray.push(promiseTRSANSG(4)); promisesArray.push(promiseTRSANSG(2)); var handleAllPromises = Promise.all(promisesArray); handleAllPromises.then(function(values) { console.timeEnd("Promise.All"); console.log("All the promises are resolved", values); }); handleAllPromises.catch(function(reason) { console.log("One of the promises failed with the following reason", reason); }); 

This is what this code will bring to the console:


All promises are allowed.

After analyzing the results of this example, you can make two important observations.

First, the third promise, the resolution of which takes 2 seconds, is completed before the completion of the second, but, as can be seen from the output generated by the code, the order of promises in the array is preserved.

Secondly, there is a timer in the code, which is used to find out how long it takes to execute the Promise.all instruction.

If the promises were performed sequentially, the execution time of this instruction would be 7 seconds (1 + 4 + 2). However, the timer tells us that the whole operation took 4 seconds, if we round the result. This is proof that all promises are executed in parallel.

Example # 2


Now consider the situation when there is no Promise in the array passed to Promise.all. I suppose such an application of this function is the least likely.

 console.time("Promise.All"); var promisesArray = []; promisesArray.push(1); promisesArray.push(4); promisesArray.push(2); var handleAllPromises = Promise.all(promisesArray); handleAllPromises.then(function(values) { console.timeEnd("Promise.All"); console.log("All the promises are resolved", values); }); handleAllPromises.catch(function(reason) { console.log("One of the promises failed with the following reason", reason); }); 

Here is the output that this code will generate:


Calling Promise.all with the transfer to this method of an array that does not contain promises

Since there are no promises in the array, Promise.all almost instantly resolved.

Example number 3


Now let's take a look at what happens when one of the promises transmitted by Promise.all is rejected.

 console.time("Promise.All"); var promisesArray = []; promisesArray.push(promiseTRSANSG(1)); promisesArray.push(promiseTRSANSG(5)); promisesArray.push(promiseTRSANSG(3)); promisesArray.push(promiseTRJANSG(2)); promisesArray.push(promiseTRSANSG(4)); var handleAllPromises = Promise.all(promisesArray); handleAllPromises.then(function(values) { console.timeEnd("Promise.All"); console.log("All the promises are resolved", values); }); handleAllPromises.catch(function(reason) { console.timeEnd("Promise.All"); console.log("One of the promises failed with the following reason ", reason); }); 

As can be seen from the results of executing the code shown below, the implementation of Promise.all stops after the first rejected promise with a message that gives this promise.


Execution stops after the first rejected promise.

Prom Promise.race Method


MDN reports that the Promise.race(iterable) method returns the allowed or rejected promis with the value or cause of the rejection, after one of the transmitted promises is, respectively, allowed or rejected.

Consider the examples of working with Promise.race .

Example â„–1


It shows what happens when one of the promises passed to Promise.race is resolved before anyone else.

 console.time("Promise.race"); var promisesArray = []; promisesArray.push(promiseTRSANSG(4)); promisesArray.push(promiseTRSANSG(3)); promisesArray.push(promiseTRSANSG(2)); promisesArray.push(promiseTRJANSG(3)); promisesArray.push(promiseTRSANSG(4)); var promisesRace = Promise.race(promisesArray); promisesRace.then(function(values) { console.timeEnd("Promise.race"); console.log("The fasted promise resolved", values); }); promisesRace.catch(function(reason) { console.timeEnd("Promise.race"); console.log("The fastest promise rejected with the following reason ", reason); }); 

This is what gets into the console after executing this example.


Promise, which was resolved faster than all the others

All promises are performed in parallel here. The third promise is allowed in 2 seconds. Once this happens, the promise returned by Promise.race is resolved.

Example 2


Now consider the situation when one of the promises transmitted by Promise.race is rejected.

 console.time("Promise.race"); var promisesArray = []; promisesArray.push(promiseTRSANSG(4)); promisesArray.push(promiseTRSANSG(6)); promisesArray.push(promiseTRSANSG(5)); promisesArray.push(promiseTRJANSG(3)); promisesArray.push(promiseTRSANSG(4)); var promisesRace = Promise.race(promisesArray); promisesRace.then(function(values) { console.timeEnd("Promise.race"); console.log("The fasted promise resolved", values); }); promisesRace.catch(function(reason) { console.timeEnd("Promise.race"); console.log("The fastest promise rejected with the following reason ", reason); }); 

After executing this example, the console will get the following:


Promise rejected before anyone else

The promises here, as in the previous examples, are performed in parallel. The fourth promise is rejected after 3 seconds. Once this happens, the promise returned by Promise.race is rejected.

General example and experiments


I collected all the examples that we considered in this material in one place, which will make it more convenient to experiment with them, to explore various scenarios of working with promises. This code is designed for execution in the browser, so here we do not use any API calls, do not use file operations, and do not work with databases. Although all this is used in the development of real projects, I believe that working with these mechanisms may distract us from our main goal - promises. And the use of simple functions, simulating, time delays, gives similar results and does not burden us with additional details.

Independently exploring these examples, you can experiment with the code, with the values ​​of variables, study different scenarios for using promises. In particular, you can use a combination of promiseTRJANSG , promiseTRSANSG and promiseTRRARNOSG to simulate a number of promise usage scenarios that will allow you to better understand them. In addition, note that using the console.time command allows you to figure out the time it takes to execute a certain piece of code, and, for example, find out whether promises are performed in parallel or sequentially. Here is the link to the gist page with the code. And by the way, if you like, take a look at the Bluebird library, which contains some interesting methods for working with promises.

Results


I offer you a list of rules that I follow when working with promises in order to use them correctly.

  1. Use promises in situations where you are working with asynchronous or blocking code.
  2. To handle the situation of successful resolution of the promise, use the .then method, for those cases when the promise is rejected, use the .catch .
  3. Use the .then and .catch in all promises.
  4. If you need to do something when resolving and rejecting promise, use the .finally method.
  5. , , , .
  6. , .
  7. Promise , , , .
  8. Promise.all , .

, , , .

Dear readers! , , ?

- ,
- 10% :



:)

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


All Articles