A song takes you to understand Promise

Promise introduction

Promise is a solution for asynchronous programming, which is more reasonable and powerful than traditional solutions - callback functions and events. (excerpted from < ECMAScript 6 Introduction >), but for the creation and execution process of promise, many people are easy to get into misunderstandings when they first learn.

Today we will take you to learn Promise with a song.

Promise create

Promise translates to mean "commitment". In fact, the creation of promise is essentially a process of making commitments. Thinking of this, I can't help but think of a classic old song, your commitment by Hai Mingwei.

The two people who knew each other and loved each other in the song finally separated and made a commitment to "don't be sad for each other and live their own lives". But then the Song said, "Oh baby, I remember what you promised me, but you forgot your commitment. It's not that we'll no longer contact each other and no one will make mistakes", suggesting that the state of commitment has changed.

Therefore, it is not difficult to imagine the following two situations:

  1. Keep promise

  1. Breach of promise

Similarly, the creation of Promise in JS is also a way to make code commitments. It also contains three states by default:

  1. pending (in progress)
  2. Fully completed
  3. rejected (failed)

So how to make commitments in JS:

var p = new Promise(function(){
 // Callback function
})

Promise receives a callback function (simply a container) and passes in the result of an event (usually an asynchronous operation) that will not end in the future. At this time, print and you will get a promise instance (instantiated object). After expansion, it will be displayed by default:

Promise {<pending>}
 [[Prototype]]: Promise,
 [[PromiseState]]: "pending",
 [[PromiseResult]]: undefined,

At this point we will see

[[PromiseState]]For storage Promise example(Current commitments)State of ,Default display pending ;
[[PromiseResult]]Used to represent Promise Data stored in the instance , Default display undefined;
[[Prototype]]The prototype will be explained later

However, some people will ask, since there are three states, and the default is pending, how can we change the state of the current Promise instance to the other two:

Therefore, we have to mention the first feature of Promise:

The state of the object is not affected by the outside world. The Promise object represents an asynchronous operation and has three states: pending, successful and rejected. Only the result of the asynchronous operation can determine which state it is currently in, and no other operation can change this state.

It can be understood that the state does not change by itself, but is determined by the result of asynchrony (synchronization is also OK).

Therefore, during Promise creation, the incoming callback function receives two methods that change the state, resolve and reject, as formal parameters

var p = new Promise(function(resolve,reject){//Formal parameters
 // Callback function
})

resolve() changes the state of Promise instance from pending to completed

reject() changes the state of the Promise instance from pending to rejected

In addition, the resolve() reject() method can also receive a parameter when calling, which is passed in as data and stored in the Promise instance.

Synchronization code display:

var p = new Promise(function(resolve,reject){//Formal parameters
 // Callback function
 resolve(111)
})
console.log(p)
result:
Promise {<fulfilled>: 111}
    [[Prototype]]: Promise
    [[PromiseState]]: "fulfilled"   // Succeeded
    [[PromiseResult]]: 111          // Store data
    
var p = new Promise(function (resolve, reject) {//Formal parameters
    // Callback function
    reject(222)
})
Promise {<rejected>: 111}
 [[Prototype]]: Promise
 [[PromiseState]]: "rejected"   // Failed
 [[PromiseResult]]: 222         // Store data
 
 Uncaught (in promise) 111         // Error reporting (you can ignore it and it will be caught by the then() / catch() method later)

resolve() and reject() methods are invoked in synchronous code, which immediately change the status of Promise instances.

Asynchronous code presentation:

var p = new Promise(function (resolve, reject) {//Formal parameters
    setTimeout(function () {
        resolve(111);
        console.log("line 24:", p);
    }, 2000)
})
console.log("line 27:", p);

The results are as follows::
line 27: Promise {<pending>}
line 24: Promise {<fulfilled>: 111}

-------------------------------------Please execute the two codes separately-------------------------------------------------

var p = new Promise(function (resolve, reject) {//Formal parameters
    setTimeout(function () {
        reject(111);
        console.log("line 24:", p);
    }, 2000)
})
console.log("line 27:", p);

The results are as follows::
line 27: Promise {<pending>}
...2s after
line 24: Promise {<rejected>: 111}
report errors: 2.html:60 Uncaught (in promise) 111    (You can ignore what will be then() / catch() Method capture)

resolve() and reject() methods are invoked in asynchronous code: because JS is a single thread, it will give priority to executing synchronous code on the main thread, and asynchronous code will be placed in the task queue, so when the page is loaded, the Promise instance is pending (in progress). Status, wait for the main thread to execute, and the task queue notifies the main thread that the asynchronous task can be executed before the task enters the main thread for execution.

At this time, the callback function in setTimeout is called and the resolve() or reject() method is executed. The Promise instance will change its state and store the incoming data.

Can the resolve() reject() method be called repeatedly?

This brings us to the second feature of Promise: once the state changes, it will not change again, and this result can be obtained at any time. There are only two possibilities for the state of Promise objects to change: from pending to fully and from pending to rejected. As long as these two situations occur, the state will solidify, will not change again, and will always maintain this result. At this time, it is called resol Ved (finalized) note that for the convenience of writing, the resolved in the later part of this chapter only refers to the fully qualified state, not the rejected state.

It can be seen that Promise is essentially a state machine. After the Promise object is created, its state is pending, and then the program controls whether the execution has been completed or failed.

Because Promise handles asynchronous tasks, we also have to listen to Promise. When the state of Promise changes, we need to execute the corresponding function (then catch finally).

Promise's dynamic method

The so-called Promise dynamic method means that the encapsulated method is stored on the Promise prototype object for use by all instantiated objects created through the constructor.

Promise.prototype.then()

The Promise instance has a then method, which is used to add a callback function when the state of the Promise instance changes. As mentioned earlier, the state of the Promise instance can change from pending (in progress) to completed (succeeded) or rejected (failed), so the then method receives two callback functions (both of which are optional).

p.then(resolveHandler,rejectHandler)

The first parameter of resolveHandler is used to specify the callback function to be executed when the Promise instance changes from pending to completed.

rejectHandler second parameter, used to specify the callback function to be executed when the Promise instance changes from pending (in progress) to rejected (failed).

It can be understood that the two callback functions in the then() method specify the contents to be executed when the state changes in advance, and then execute the corresponding callback function after waiting for the state of the Promise instance in the future (it is still a callback function in essence, and magic should be defeated by magic).

Note: a formal parameter can be passed into resolveHandler rejectHandler to receive the data stored in Promise instance.

// Wait for two seconds and a random number of 0-1. If num > = 0.5, it becomes fully, otherwise it becomes rejected
var p = new Promise(function (resolve, reject) {//Formal parameters
        setTimeout(function () {
            var num = Math.random();
            if (num >= 0.5) {
                resolve(111)
            } else {
                reject(2222);
            }
            console.log("line 24:", p);
        }, 2000)
    })
console.log("line 27:", p);

p.then(function (arg) {
 console.log("line 50", arg);
}, function (arg) {
 console.log("line 52", arg);
})

Case 1: (num>=0.5)  
    line 27: Promise {<pending>}
    ...2 Seconds later
    line 24: Promise {<fulfilled>: 111}
    line 50 111
    
Case 2: (num<0.5) 
 line 27: Promise {<pending>}
 ...2 Seconds later
    2.html:81 line 24: Promise {<rejected>: 2222}
    2.html:89 line 52 2222

Parsing: calling in asynchronous code: because JS is a single thread, it will give priority to executing synchronous code on the main thread, and asynchronous code will be placed in the task queue.

  1. After the Promise is created, the Promise instance is in pending status;
  2. Call the then() method, pass in two callback functions, specify the contents to be executed when the state changes in advance, and execute the corresponding callback function after waiting for the state of the Promise instance in the future;
  3. Wait until the main thread finishes executing, and the task queue notifies the main thread that the asynchronous task can be executed before the task enters the main thread for execution. = > Randomly a random number of 0-1, change the state of Promise instance according to the result, store the incoming data = > execute the corresponding callback function in the then method.

Another interesting thing: the then method returns a new Promise instance (note that it is not the original Promise instance). Therefore, you can use chain writing, that is, the then method can be called later by another then method.

    var p = new Promise(function (resolve, reject) {//Formal parameters
        setTimeout(function () {
            resolve(1);
        }, 2000);
    })

    p.then(function (num) {
        console.log("line 64:", num);
        return 2;
    }).then(function (num) {
        console.log("line 67:", num);
        return 2;
    })
    result:
    line 64: 1
    line 67: 2

The above code uses the then method to specify two callback functions in turn. After the first callback function is completed, the returned result will be passed into the second callback function as a parameter. Of course, there is a premise here, that is, when the Promise instance status is successful and there is no error during code execution. In case of an error, we can use the second callback function of the then() method or the catch() method to catch the error.
Promise.prototype.catch()

The catch() method is an alias for. then(null, rejection) or. then(undefined, rejection) to specify the callback function when an error occurs.

 function readText(url) {
        var p = new Promise(function (resolve, reject) {
            $.ajax({
                type: "get",
                url: url,
                success: function (text) {
                    resolve(text);
                },
                error:function(err){
                 reject(err);
                }
            })
        })
        return p; // {pending}
    }
    readText("../data/1.txt").then(res => {
        console.log("line 93", res)
    }).catch(err => {
        console.log(err);
    })

In the above code, the readText() method returns a Promise object. If the state of the object changes to resolved, the callback function specified by the then() method will be called; If the asynchronous operation throws an error, the status will change to rejected, and the callback function specified by the catch() method will be called to handle the error. In addition, if the callback function specified by the then() method throws an error during operation, it will also be caught by the catch() method.

Chain operation of then and catch

When then() and catch() are used together, the chain operation effect is better.

    var p = new Promise(function (resolve, reject) {
        setTimeout(function () {
            resolve(1);
        }, 2000);
    })

    console.log("line 21", p);
    p.then(function (num) {
        console.log("line 23:", num);
        return 2;
    }).then(function (num) {
        console.log("line 25:", num);
    }).then(function (num) {
        console.log("line 28:", num);
    }).catch(function (err) {
        console.log(err);
    })
    
result:
    line 21 Promise {<pending>}
    ...Wait 2 s after
    line 23: 1
    line 25: 2
    line 28: 3

The above code uses the then catch method to make a chain call, specifying three then() callback functions and a catch() callback function in turn. After the completion of the first callback function, the returned result will be passed into the second callback function as a parameter. Similarly, after the completion of the second callback function, the returned result will be passed into the third callback function as a parameter, and so on. Of course, there is also a premise here, that is, when the Promise instance status is successful and there is no error during code execution. In case of an error, the catch() method just catches the error during the chain operation.

If the return value of the callback function in the then method is also a Promise instance;

function readText(url) {
        var p = new Promise(function (resolve, reject) {
            $.ajax({
                type: "get",
                url: url,
                success: function (text) {
                    resolve(text);
                },
                error: function (err) {
                    reject(err);
                }
            })
        })
        return p; // {pending}
    }

    // Solution to ajax terror callback 1:
    // T = T1 + T2 + T3 (time)
    var str = "";
    readText("../data/1.txt").then(txt => {
        console.log("line 36", txt);
        str += txt;
        return readText("../data/2.txt");
    }).then(txt => {
        console.log("line 40", txt);
        str += txt;
        return readText("../data/3.txt");
    }).then(txt => {
        console.log("line 44", txt);
        str += txt;
        return str;
    }).then(txt => {
        console.log("line 48", txt);
    }).catch(err => {
        console.log("fail:", err);
    })
    
result:
    line 36 11111
  line 40 22222
 line 44 33333
 line 48 111112222233333  

In the above code, the callback function specified by the first then method returns another Promise object. At this time, the callback function specified by the second then method will wait for the state of the new Promise object to change. If it changes to resolved, continue to chain call the callback function specified in the then method. If the status changes to rejected, it will be captured by the catch method.

Note: the error of Promise instance is "bubbling" and will be passed back until it is caught. That is, errors are always caught by the next catch statement.

Promise static method

The so-called Promise static method means that the encapsulated method is stored on the constructor Promise and called by the constructor itself.

Promise.all

The Promise.all() method is used to wrap multiple Promise instances into a new Promise instance.

const p = Promise.all([p1, p2, p3]);

Compared with the chain operation of the then method, Promise.all() pays more attention to concurrency, that is, send multiple requests in sequence, wait for all requests to have results, and then store the data in the array in the corresponding request order.

Analog packaging(Simple version)
  Promise.myAll = function (list) {
        return new Promise(function (resolve, reject) {  // Returns a new Promise instance
            var arr = [];
            for (let i = 0; i < list.length; i++) {
                let p = list[i];// Each Promise instance (if the incoming data is not Promise, the instance will be converted through Promise.resolve)
                
                // Specify the content to be executed when each instance succeeds and fails in advance = > the request will be executed after the request has a result
                p.then(res => {  //Wait for the result of the asynchronous operation, and then put the corresponding subscript into the array
                    arr[i] = res;
                
                    if (arr.length === list.length) { // All requests are successful = > (also an asynchronous operation)
                        resolve(arr);
                    }
                }).catch(err => {    //As long as there is a failure, go catch  
                    console.log(err);
                    reject(err);   
                })
            }
        })
 }
 
 const p = Promise.myAll([p1, p2, p3]);   

Through the above code, you can find: (excerpted from ECMAScript 6 getting started)

Promise.all() method accepts an array as a parameter. p1, p2 and p3 are promise instances. If not, it will first call Promise.resolve method mentioned below to convert the parameter into promise instance, and then proceed further. In addition, the parameter of Promise.all() method can not be an array, but it must have an Iterator interface, and each member returned is a promise instance.

The state of p is determined by p1, p2 and p3, which can be divided into two cases.

(1) Only when the states of p1, p2 and p3 become fully, the state of p will become fully. At this time, the return values of p1, p2 and p3 form an array and are passed to the callback function of p.

(2) As long as one of p1, p2 and p3 is rejected, the status of p becomes rejected. At this time, the return value of the first rejected instance will be passed to p's callback function.

Promise.race

The Promise.race() method also packages multiple Promise instances into a new Promise instance.

const p = Promise.race([p1, p2, p3]);

Compared with Promise.all method, Promise.race method focuses more on the speed of state change.

Analog packaging(Simple version)
 Promise.myRace = function (list) {
    return new Promise(function (resolve, reject) {  // Returns a new Promise instance
         for (let i = 0; i < list.length; i++) {
             let p = list[i];// Each Promise instance (if the incoming data is not Promise, the instance will be converted through Promise.resolve)
 
             // Specify the content to be executed when each instance succeeds and fails in advance = > the request will be executed after the request has a result
             p.then(res => {  //Wait for the result of the asynchronous operation, and then put the corresponding subscript into the array
              resolve(res);
             }).catch(err => {
              reject(err);
             })
       }
    })
}

const p = Promise.myRace([p1, p2, p3]);

Through the above code, you can find: (excerpted from ECMAScript 6 getting started)

As long as one of p1, p2 and p3 takes the lead in changing the state, the state of p will change accordingly. The return value of the Promise instance that changed first is passed to the callback function of p.

Promise.allSettled

Promise.allSettled() method also packages multiple promise instances into a new promise instance.

const p = Promise.allSettled([p1, p2, p3]);

Compared with Promise.all method, Promise.allSettled focuses on putting the results into the array in the order of request no matter whether each Promise instance succeeds or fails.

Analog packaging(Simple version)
 Promise.myAllSettled = function (list) {
         return new Promise(function (resolve, reject) {  // Returns a new Promise instance
             var arr = [];
             for (let i = 0; i < list.length; i++) {
                 let p = list[i];// Each Promise instance (if the incoming data is not Promise, the instance will be converted through Promise.resolve)
 
                 // Specify the content to be executed when each instance succeeds and fails in advance = > the request will be executed after the request has a result
                 p.then(res => {  //Wait for the result of the asynchronous operation, and then put the corresponding subscript into the array
                     var obj = { status: "fulfilled", value: txt };
                     arr[i] = obj;
                     if (arr.length === list.length) { // All requests succeeded = > arr (asynchronous)
                         resolve(arr);
                     }
                 }).catch(err => {
                     var obj = { status: "rejected", reason: err };
                     arr[i] = obj;
                     if (arr.length === list.length) { // All requests succeeded = > arr (asynchronous)
                         resolve(arr);
                     }
                 })
             }
         })
     }
     const p =  Promise.myAllSettled([p1, p2, p3])

It is worth noting that in the process of putting data into the array, the status of the incoming Promise instance is different, and the placement results are slightly different (each member in the array is an object, and the format of the object is fixed).

// When the asynchronous operation is successful
{status: 'fulfilled', value: value}

// When an asynchronous operation fails
{status: 'rejected', reason: reason}

The value of the status attribute of the member object can only be the string fully or the string rejected, which is used to distinguish whether the asynchronous operation succeeds or fails. If it is successful, the object will have a value attribute. If it is rejected, it will have a reason attribute, which corresponds to the return value of the previous asynchronous operation in the two states.

summary

With Promise objects, asynchronous operations can be expressed in the process of synchronous operations, avoiding layers of nested callback functions. In addition, Promise objects provide a unified interface, making it easier to control asynchronous operations.

Promise also has some disadvantages. First of all, promise cannot be cancelled. Once it is created, it will be executed immediately. It cannot be cancelled halfway. Secondly, if the callback function is not set, the errors thrown by promise will not be reflected to the outside. Third, when it is in the pending state, it is impossible to know which stage it has reached (just started or about to be completed).

Upgrade usage async await

ES2017 standard introduces async function, which makes asynchronous operation more convenient.

What is the async function? In a word, it is the syntax of the Generator function.

Basic Usage

 async function fn() {
        var str = "";
        var text = await readText("../data/1.txt"); // Promise instance
        console.log("line 29:",text);
        str += text;

        var text = await readText("../data/2.txt"); // Promise instance
        console.log("line 33:",text);
        str += text;

        var text = await readText("../data/3.txt"); // Promise instance
        console.log("line 37:",text);
        str += text;

        return str;
    }
    
    fn().then(res=>{
     console.log("line 44:",res)
    }).catch(err=>{
     console.log("line 46:",err)
    })
    
    result:
    line 29: 11111
 line 33: 22222
 line 37: 33333
 line 44: 111112222233333   

The return value of async function is also a Promise instance. During the execution of async function, once await is encountered, the Promise instance in pending status will be returned first. After the asynchronous operation has results, continue to execute the statements after await, and so on. When all the statements are executed and there are no errors, the returned Promise instance will become successful, Otherwise, it will become failed.

Have you learned? The message at the bottom of the article tells me yo.

Keywords: Front-end

Added by mad_hacker on Fri, 26 Nov 2021 08:56:54 +0200