Deep understanding of Promise, the core of JS asynchronous programming

In fact, before the emergence of ES6 standard, the community first put forward the Promise scheme. Later, with the addition of ES6, its usage was unified and native Promise objects were provided.

Promise basic information

If you have to explain what Promise is, it is simply a container that holds the results of an event (usually asynchronous operation) that will not end in the future. Syntactically speaking, Promise is an object from which you can get the message of asynchronous operation.

Promise provides a unified API, and various asynchronous operations can be processed in the same way. Let's take a brief look at the chain calling code of promise implementation, as shown below.

function read(url) {
    return new Promise((resolve, reject) => {
        fs.readFile(url, 'utf8', (err, data) => {
            if(err) reject(err);
            resolve(data);
        });
    });
}

read(A).then(data => {
    return read(B);
}).then(data => {
    return read(C);
}).then(data => {
    return read(D);
}).catch(reason => {
    console.log(reason);
});

Combined with the above code, let's analyze the internal state flow of Promise. The Promise object is in a pending state when it is created. It allows you to associate the final success value or failure reason returned by asynchronous operation with the corresponding handler.

If this content is helpful to you, please use your small hand to help add a chicken leg for Xiaobian by clicking this link: http://github.crmeb.net/u/xingfu
Generally, Promise is bound to be in one of the following states during its implementation.

  1. pending: the initial state, which is neither completed nor rejected.
  2. Completed: the operation completed successfully.
  3. rejected: operation failed.

If the Promise object in the pending state is executed, it will be completed by a value or rejected for a reason. When one of these situations occurs, the relevant handlers arranged with Promise's then method will be called. Because Promise prototype. Then and Promise prototype. The catch method returns a Promise, so they can continue to be called chained.

It's worth noting that the state of Promise is not reversible in the process of programming. The text description is rather obscure. We can clearly see the internal state flow of Promise through a picture directly, as shown below (the picture comes from the network).

How Promise solves callback hell

First of all, please recall what is callback hell. Callback hell has two main problems:

  1. The problem of multi-layer nesting;
  2. There are two possibilities (success or failure) for the processing results of each task, so you need to deal with these two possibilities respectively after the execution of each task.

These two problems are particularly prominent in the "era of callback functions", and promise was born to solve these two problems. Promise uses three technical means to solve the callback Hell: callback function delayed binding, return value penetration and error bubbling.

Let's illustrate it with a piece of code, as shown below.

let readFilePromise = filename => {
  return new Promise((resolve, reject) => {
    fs.readFile(filename, (err, data) => {
      if (err) {
        reject(err)
      } else {
        resolve(data)
      }
    })
  })
}

readFilePromise('1.json').then(data => {
  return readFilePromise('2.json')
});

From the above code, we can see that the callback function is not directly declared, but passed in through the later then method, that is, delayed incoming, which is the delayed binding of callback function. Next, let's fine tune the above code, as shown below.

let x = readFilePromise('1.json').then(data => {
  return readFilePromise('2.json')  //This is the Promise returned
});
x.then(/* Internal logical ellipsis */)

We create different types of promises according to the incoming value of the callback function in then, and then penetrate the returned Promise into the outer layer for subsequent calls. x here refers to the Promise returned internally, and then chain calls can be completed successively after x. This is the effect of return value penetration. These two technologies work together to write deep nested callbacks in the following form.

If this content is helpful to you, please use your small hand to help add a chicken leg for Xiaobian by clicking this link: http://github.crmeb.net/u/xingfu

readFilePromise('1.json').then(data => {
    return readFilePromise('2.json');
}).then(data => {
    return readFilePromise('3.json');
}).then(data => {
    return readFilePromise('4.json');
});

This is refreshing. More importantly, it is more in line with people's linear thinking mode and better development experience. The combination of the two technologies produces the effect of chain call.

This solves the problem of multi-layer nesting. How to solve the other problem, that is, how to deal with success and failure respectively after each task execution? Promise adopts the wrong bubbling method. In fact, it's easy to understand. Let's see the effect.

readFilePromise('1.json').then(data => {
    return readFilePromise('2.json');
}).then(data => {
    return readFilePromise('3.json');
}).then(data => {
    return readFilePromise('4.json');
}).catch(err => {
  // xxx
})

In this way, the previous errors will be passed back and received by catch, so there is no need to check the errors frequently. From the above codes, we can see that Promise has obvious effect: realize chain call and solve the problem of multi-layer nesting; Realize one-stop processing after error bubbling, and solve the problem of judgment error and increasing code confusion in each task.

Next, let's take a look at the static methods provided by Promise.

Promise static method

I will introduce all, allSettled, any and race methods from the aspects of syntax, parameters and method code.

all method

  • Syntax: promise all(iterable)
  • Parameter: an iteratable object, such as Array.

Description: this method is very useful for summarizing the results of multiple promises. Multiple promises can be combined in ES6 All asynchronously requests parallel operations, and the returned results are generally in the following two cases.

  1. When all results are returned successfully, success is returned in the order of request.
  2. When there is a failed method, enter the failed method.

Let's take a look at the business scenario. For the loading of the following business scenario page, it may be better to combine multiple requests and implement them with all. Please see the code fragment.

//1. Obtain the rotation data list

function getBannerList(){
  return new Promise((resolve,reject)=>{
      setTimeout(function(){
        resolve('Rotation data')
      },300) 
  })
}

//2. Get the list of stores

function getStoreList(){
  return new Promise((resolve,reject)=>{
    setTimeout(function(){
      resolve('Store data')
    },500)
  })
}

//3. Get classification list

function getCategoryList(){
  return new Promise((resolve,reject)=>{
    setTimeout(function(){
      resolve('Classified data')
    },700)
  })
}

function initLoad(){ 
  Promise.all([getBannerList(),getStoreList(),getCategoryList()])
  .then(res=>{
    console.log(res) 
  }).catch(err=>{
    console.log(err)
  })
} 

initLoad()

If this content is helpful to you, please use your small hand to help add a chicken leg for Xiaobian by clicking this link: http://github.crmeb.net/u/xingfu
As can be seen from the above code, the three operations of obtaining the rotation list, obtaining the store list and obtaining the classification list need to be loaded in a page. The page needs to send a request for page rendering at the same time. In this way, promise All, which looks clearer and clearer at a glance.

Let's look at another method.

allSettled method

Promise. The syntax and parameters of allsettled are the same as promise Similar to all, its parameters accept an array of promises and return a new promise. The only difference is that it will not fail after execution, that is, when promise After allsettled is processed, we can get the status of each promise, regardless of whether it is processed successfully or not.

Let's take a look at a piece of code implemented with allSettled.

const resolved = Promise.resolve(2);
const rejected = Promise.reject(-1);
const allSettledPromise = Promise.allSettled([resolved, rejected]);
allSettledPromise.then(function (results) {
  console.log(results);
});

// Return result:
// [
//    { status: 'fulfilled', value: 2 },
//    { status: 'rejected', reason: -1 }
// ]

As you can see from the above code, Promise Allsettled finally returns an array that records the return value of each Promise in the parameters passed in, which is different from the all method. You can also modify the code of the business scenario provided by the all method. In fact, you can also know that after multiple requests are sent, Promise finally returns the final state of each parameter.

Next, let's take a look at any.

any method

  • Syntax: promise any(iterable)
  • Parameter: iteratable object, such as Array.

Description: any method returns a Promise. As long as one of the parameter Promise instances becomes in the full state, the last instance returned by any will become in the full state; If all parameter Promise instances become rejected, the wrapper instance will become rejected.

Let's take a look at the modified code and the execution results.

const resolved = Promise.resolve(2);
const rejected = Promise.reject(-1);
const anyPromise = Promise.any([resolved, rejected]);
anyPromise.then(function (results) {
  console.log(results);
});

// Return result:
// 2

It can be seen from the modified code that as long as one of the promises becomes in the fully fulfilled state, any will finally return the Promise. Since the above resolved Promise is already resolved, the final returned result is 2.

Let's finally look at the race method.

race method

  • Syntax: promise race(iterable)
  • Parameter: iteratable object, such as Array.

Description: the race method returns a Promise. As long as one instance of the Promise of the parameter changes the state first, the return state of the race method will change accordingly. The return value of the Promise instance that changes first is passed to the callback function of the race method.

Let's take a look at this business scenario. For the loading of pictures, it is particularly suitable to use race method to solve it. Put the picture request and timeout judgment together, and use race to realize the timeout judgment of pictures. Look at the code snippet.

//Request a picture resource

function requestImg(){
  var p = new Promise(function(resolve, reject){
    var img = new Image();
    img.onload = function(){ resolve(img); }
    img.src = 'http://www.baidu.com/img/flexible/logo/pc/result.png';
  });

  return p;
}

//Delay function, which is used to time the request

function timeout(){
  var p = new Promise(function(resolve, reject){
    setTimeout(function(){ reject('Picture request timeout'); }, 5000);
  });

  return p;
}

Promise.race([requestImg(), timeout()])
.then(function(results){
  console.log(results);
})

.catch(function(reason){
  console.log(reason);
});

If this content is helpful to you, please use your small hand to help add a chicken leg for Xiaobian by clicking this link: http://github.crmeb.net/u/xingfu
As can be seen from the above code, Promise is also used to judge whether the picture is loaded successfully Race method is a good business scenario.

To sum up, the parameter transfer forms of the four methods are basically the same, but in the end, the functions implemented by each method are slightly different, which you need to pay attention to.

summary

OK, that's all for this lecture. In these two lectures, I took you through Promise's asynchronous programming method. I hope you can form a deeper understanding of it. As for how to realize a Promise that meets the specifications, I will take you step by step in the later advanced courses. These two lectures also lay the foundation for the later practice, so I hope you can master it well.

Finally, I sorted out several methods of Promise. You can review them again according to the table below.

If this content is helpful to you, please use your small hand to help add a chicken leg for Xiaobian by clicking this link: http://github.crmeb.net/u/xingfu

Keywords: Javascript Design Pattern Promise

Added by storyboo on Sat, 19 Feb 2022 00:41:09 +0200