Understand the promises in javascript

https://blog.bitsrc.io/understanding-promises-in-javascript-c5248de9ff8f
  • Transfer
Good day, Habr! I present to you the translation of the article “Understanding Promises in JavaScript” by Sukhjinder Arora.



From the author of the translation: Just like the author himself, I hope that the article was useful for you. Please, if she really helped you learn something new for yourself, then do not be lazy to go to the original article and thank the author! I will be glad to your feedback!

Link to the translation of the article on asynchronous JavaScript from the same author .


JavaScript is a single-threaded programming language, which means that one thing can be executed at once. Before ES6, we used callbacks to manage asynchronous tasks, such as a network request.

Using promises, we can avoid the “hell of callbacks” and make our code cleaner, more readable and easier to understand.

Suppose we want to asynchronously get some data from the server using callbacks, we would do something like this:

getData(function(x){
    console.log(x);
    getMoreData(x, function(y){
        console.log(y); 
        getSomeMoreData(y, function(z){ 
            console.log(z);
        });
    });
});

Here I request some data from the server using the getData () function , which gets the data inside the callback function. Inside the callback function, I request additional data by calling the getMoreData () function , passing previous data as an argument, and so on.

This is what we call the “callback hell”, where each callback is nested inside the other, and each internal callback depends on its parent.

We can rewrite the above fragment using promises:

getData()
  .then((x) => {
    console.log(x);
    return getMoreData(x);
  })
  .then((y) => {
    console.log(y);
    return getSomeMoreData(y);
  })
  .then((z) => {
    console.log(z);
   });

You can see what has become more readable than in the case of the first example with callbacks.

What are Promises?


Promis (Promise) is an object that contains the future value of an asynchronous operation. For example, if you request some data from the server, promise promises us to get this data, which we can use in the future.

Before plunging into all these technical things, let's look at the terminology of the promises.

States of promises


Promis in JavaScript, like a promise in real life, has 3 states. This can be 1) unresolved (pending), 2) resolved / resolved (3) or 3) rejected.



Unsolved or Pending  - Promis waits if the result is not ready. That is, it expects the completion of something (for example, the completion of an asynchronous operation).
Solved or Executed  - Promis resolved if the result is available. That is, something completed its execution (for example, an asynchronous operation) and everything went well.
Rejected  - Promis rejected if an error occurred during execution.

Now that we know what Promis and its terminology are, let's go back to the practical part of promises.

Create a Promise


In most cases, you will simply use promises rather than create them, but it is still important to know how they are created.

Syntax:

const promise = newPromise((resolve, reject) => {
    ...
  });

We have created a new promise using the constructor constructor, it accepts one argument, a callback, also known as an executive function, which accepts 2 callbacks, resolve and reject .

The executive function is performed immediately after the creation of the promise. Promis is executed by calling resolve () , and rejected by reject () . For example:

const promise = newPromise((resolve, reject) => {
  if(allWentWell) {
    resolve('Все прошло отлично!');
  } else {
    reject('Что-то пошло не так');
  }
});

The resolve () and reject () take one argument, which can be a string, a number, a logical expression, an array, or an object.

Let's take a look at another example to fully understand how promises are created.

const promise = newPromise((resolve, reject) => {
  const randomNumber = Math.random();
setTimeout(() => {
    if(randomNumber < .6) {
      resolve('Все прошло отлично!');
    } else {
      reject('Что-то пошло не так');
  }
  }, 2000);
});

Here I created a new promise using the designer Promisov. Promis is executed or rejected 2 seconds after its creation. A prompt is executed if randomNumber is less than .6 and is rejected in other cases.

When the promise has been created, it will be in a wait state and its value will be undefined .


After 2 seconds, the timer ends, promis randomly either executed or rejected, and its value will be the one passed to the function resolve or reject . Below is an example of two cases:

Successful execution:



Promis rejection:



Note: Promis can be executed or rejected only once. Further calls of resolve () or reject () will not affect the state of promis. Example:

const promise = newPromise((resolve, reject) => {
  resolve('Promise resolved');  // Промис выполнен
  reject('Promise rejected');   // Промис уже не может быть отклонен
});

Since resolve () was called first, the promise now turns out to be “executed”. A subsequent call to reject () will not affect the state of the promise.

Using Promise


Now we know how to create promises, let's now figure out how to apply the already created promises. We use promises using the then () and catch () methods .

For example, querying data from an API using fetch , which returns a promise.

.then () syntax: promise.then (successCallback, failureCallback)

successCallback is called if the promise was successfully completed. Takes one argument, which is the value passed to resolve () .

failureCallback is called if the promise was rejected. Accepts one argument, which is the value of a devotee in reject () .

Example:

const promise = newPromise((resolve, reject) => {
  const randomNumber = Math.random();
  if(randomNumber < .7) {
    resolve('Все прошло отлично!');
  } else {
    reject(newError('Что-то пошло не так'));
  }
});
promise.then((data) => {
  console.log(data);  // вывести 'Все прошло отлично!'
  },
  (error) => {
  console.log(error); // вывести ошибку
  }
);

If the promise was executed, then successCallback is called with the value passed to resolve () . And if the promise was rejected, then failureCallback is called with the value passed to reject ().

.catch () syntax: promise.catch (failureCallback)

We use catch () for error handling. This is more readable than error handling inside failureCallback inside the then () method callback .

const promise = newPromise((resolve, reject) => {
reject(newError('Что-то пошло не так'));
});
promise
  .then((data) => {
     console.log(data);
   })
  .catch((error) => {
     console.log(error); // вывести ошибку
  });

Chain of promises


The then () and catch () methods can also return a new promise, which can be processed by a chain of other then () at the end of the previous then () method.

We use a chain of promises when we want to perform a sequence of promises.

For example:

const promise1 = newPromise((resolve, reject) => {
  resolve('Promise1 выполнен');
});
const promise2 = newPromise((resolve, reject) => {
  resolve('Promise2 выполнен');
});
const promise3 = newPromise((resolve, reject) => {
  reject('Promise3 отклонен');
});
promise1
  .then((data) => {
    console.log(data);  // Promise1 выполненreturn promise2;
  })
  .then((data) => {
    console.log(data);  // Promise2 выполненreturn promise3;
  })
  .then((data) => {
    console.log(data);
  })
  .catch((error) => {
    console.log(error);  // Promise3 отклонен
  });

And so, what is happening here?


When promise1 is executed, the then () method is called , which returns promise2.
Then, when promise2 is executed , then () is called again and returns promise3 .

Since promise3 is rejected, instead of the next then () , catch () is called , which handles the promise3 rejection .

Note: As a rule, one catch () method is enough to handle the rejection of any of the promises in the chain, if this method is at the end of it.

Common mistake


Quite a lot of newbies make a mistake by putting some promises inside the others. For example:

const promise1 = newPromise((resolve, reject) => {
  resolve('Promise1 выполнен');
});
const promise2 = newPromise((resolve, reject) => {
  resolve('Promise2 выполнен');
});
const promise3 = newPromise((resolve, reject) => {
  reject('Promise3 отклонен');
});
promise1.then((data) => {
  console.log(data);  // Promise1 выполнен
  promise2.then((data) => {
    console.log(data);  // Promise2 выполнен
    promise3.then((data) => {
      console.log(data);
    }).catch((error) => {
      console.log(error);  // Promise3 отклонен
    });
  }).catch((error) => {
    console.log(error);
  })
}).catch((error) => {
    console.log(error);
  });

Although this will work fine, it is considered a bad style and makes the code less readable. If you have a sequence of promises to perform, it would be better to put them one after the other than to put one inside the other.

Promise.all ()


This method takes an array of promises and returns a new promise that will be executed when all the promises within the array are completed or rejected as soon as the promise that is rejected is encountered. For example:

const promise1 = newPromise((resolve, reject) => {
 setTimeout(() => {
  resolve('Promise1 выполнен');
 }, 2000);
});
const promise2 = newPromise((resolve, reject) => {
 setTimeout(() => {
  resolve('Promise2 выполнен');
 }, 1500);
});
Promise.all([promise1, promise2])
  .then((data) =>console.log(data[0], data[1]))
  .catch((error) =>console.log(error));

Here, the argument inside then () is an array that contains the values ​​of promises in the same order in which they were passed to Promise.all () . (Only if all promises are performed)

Promis is rejected with the reason for the first promise to be rejected in the passed array . For example:

const promise1 = newPromise((resolve, reject) => {
 setTimeout(() => {
  resolve('Promise1 выполнен');
 }, 2000);
});
const promise2 = newPromise((resolve, reject) => {
 setTimeout(() => {
  reject('Promise2 отклонен');
 }, 1500);
});
Promise.all([promise1, promise2])
  .then((data) =>console.log(data[0], data[1]))
  .catch((error) =>console.log(error));  // Promise2 отклонен

Here we have two promises, where one runs after 2 seconds and the other deflects after 1.5 seconds. As soon as the second promise is rejected, returned from Promise.all () the promise is rejected without waiting for the first one to be executed.

This method can be useful when you have more than one promise and you want to know when all promises are completed. For example, if you are requesting data from a third-party API and you want to do something with this data only when all requests are successful.

As a result, we have Promise.all () , which is waiting for the successful execution of all promises, or terminates its execution when it detects the first failure in the array of promises.

Promise.race ()


This method accepts an array of promises and returns one new promise, which will be executed as soon as the completed promise is encountered in the array or is rejected if the rejected promise is encountered earlier. For example:

const promise1 = newPromise((resolve, reject) => {
 setTimeout(() => {
  resolve('Promise1 выполнен');
 }, 1000);
});
const promise2 = newPromise((resolve, reject) => {
 setTimeout(() => {
  reject('Promise2 отклонен');
 }, 1500);
});
Promise.race([promise1, promise2])
  .then((data) =>console.log(data))  // Promise1 выполнен
  .catch((error) =>console.log(error));

Here we have two promises, where one runs after 1 second, and the other deflects after 1.5 seconds. As soon as the first promise is completed, the promise returned from Promise.race () will have the status completed without waiting for the second promise status.

Here, the data that is passed to then () is the value of the first, executed, promise.

At the end, Promise.race () waits for the first promise and takes its status as the status of the returned promise.

Commentary by the author of the translation: Hence the name itself. Race - race

Conclusion


We learned what PROMIS are and what they eat in JavaScript. Promises are made up of two parts 1) Creating promises and 2) Using promises. Most of the time, you will use promises rather than create them, but it is important to know how they are created.

That's all, I hope this article was useful for you!

Also popular now: