One article lets you master Promise, async, await

    JS series 9.0 introduces the use of promise, async and await in detail, including the handwritten static method and extension method of promise; in addition, it also introduces the conversion between async and promise.

    1. Concept

    Promise is a solution to asynchronous programming, which can be used to solve the problem of callback hell in Ajax Promise is a built-in constructor. When using it, you need to use the new keyword to create a Promise object, and you need to pass in a callback function (called executor)
    A Promise must be in one of the following states:
    Pending: The initial state, neither fulfilled nor rejected, for example, a network request is in progress, or the timer has not expired
    Fulfilled : it is in this state when we actively call the resolve callback function, which means that the operation is successfully completed, and the callback function in .then() will be executed
    Rejected: It is in this state when we actively call the reject callback function, which means that the operation failed, and the callback function in .catch() will be executed

    The state of Promise can only be changed once, and there are only two changes: pending => fulfilled; pending => rejected (that is, only promises in the pending state can become successful or failed, and they are already in the successful/failed state The promise will no longer change to failure/success, and once the state of the Promise is determined, it cannot be changed (locked)


    2. Use of Promise

    2.1 Basic usage rules

    //Create a promise instance
    var p = new Promise((resolve, reject) => {
         //When resolve and reject exist at the same time, which callback is executed first
         resolve('success');
         reject('error');
       });
       //then method passes in two callback functions, the first callback function will be called back when Promise executes the resolve function
       // The second callback function will be called back when Promise executes the reject function
       p.then(
         (res) => {
           console.log('success', res);
         },
         (err) => {
           console.log('Failed', err);
         },
       );
    


    2.2 Timer-based asynchronous

    Promise is often placed in a function and returned out

    function timeFoo(data){
       return new Promise((resolve,reject)=>{
     setTimeout(()=>{
     if(data==='success')
     resolve('successful parameters')
     else reject('error')
     },1000)
       })
       }
       timeFoo('success').then(
     res=>console.log('success', res),
     err=>console.log('failure', err)
       )
      console.log('I am synchronous and will execute first');
    


    2.3 Use promise to encapsulate ajax

    const promiseAjax = function (method, url) {
     return new Promise((resolve, reject) => {
       const xhr = new XMLHttpRequest();
       xhr.onreadystatechange = function () {
         if (xhr. readyState === 4) {
           if (xhr.status >= 200 && xhr.status < 300) {
           //Callback when the request is successful
             resolve(JSON. parse(xhr. responseText));
            //Call reject when the request fails
           } else reject('error:' + xhr.status);
         }
           };
      xhr.open(method,url)
          xhr.send()
        });
      };
    let test=  promiseAjax('get','http://www.filltext.com/?rows=2&fname={firstName}&lname={lastName}')
    test.then(res=>{
        console.log(res);
    },err=>{
        console.log(err);
    })
    


    2.4 resolve parameter problem

    Promise.resolve(value): returns a Promise whose state is determined by the given value Promise.reject(reason): returns a Promise object with state o and passes it to the failure handler function

    • If the parameter passed in by resolve is an ordinary value or a non-Promise type object, the returned result is a successful promise object, and the passed in parameter will be used as the parameter of the then callback;

        Promise.resolve('123').then((data) => console.log(data));
        let obj = {
          name: 'zhang',
          age: '18',
        };
        Promise.resolve(obj).then((data) => console.log(data));
      
    • If another Promise is passed in resolve, the new Promise will determine the state of the original Promise

      Promise. resolve(
               new Promise((resolve,reject)=>{
                   //resolve('success')
                   reject('failed')
               })
           ).then(
               (data) => console. log(data),
               (err)=>console.log(err)
           ) //fail
      
    • If an object with a then method is passed in to resolve, the then method will be executed and the state of the Promise will be determined according to the result of the then method:

      let obj2={
           then:(resolve,reject)=>{
               resolve('successful')
               reject('failed')
           }
      }
      Promise.resolve(obj2).then(
           (data) => console. log(data),
           (err)=>console.log(err)
      )//successful
      

    PS:No matter what form the parameters passed in by Promise.reject are, they will be directly passed to the catch as parameters of the reject state


    2.5 proimse chain call to solve callback hell

    The .then() method takes two parameters, the first parameter is the callback function that handles the cashed state, and the second parameter is the callback function that handles the rejected state; each .then() method also Returns a newly generated promise object, this object can be used as a chain call

    When there is no function in .then() that can return a promise object, the chain call will directly continue to the next ring operation. Therefore, the chain call can omit all the callback functions that handle the rejected status before the last .catch()

    That is, the chain call can pass in a successful callback function through .then(), and finally use .catch() to handle the failure (also known as exception penetration)
    .finally(): Regardless of success or failure, it will be executed at the end

    //Timer simulates callback hell
       //Print 1, 2, 3, 4 every second
       setTimeout(() => {
         console. log(1);
         setTimeout(() => {
           console. log(2);
           setTimeout(() => {
             console. log(3);
             setTimeout(() => {
               console. log(4);
             }, 1000);
           }, 1000);
         }, 1000);
       }, 1000);
    

    promise.then solves callback hell

    let logNumber = function (data) {
        return new Promise((resolve, rejcet) => {
          setTimeout(() => {
            resolve(console.log(data));
            rejcet('error');
          }, 1000);
        });
      };
    
      logNumber(1)
        .then(() => {
          return logNumber(2);
        })
        .then(() => {
          return logNumber(3);
        })
        .then(() => {
          return logNumber(4);
        });
    


    The .catch method will also return a Promise object, so you can continue to call the then method or the catch method after the catch method; If you want to break the promise chain so that the request will not continue after the request fails, you need to throw an exception

    (Break the promise chain: return new Promise(() => {}) but using this method will not execute.finally)

    logNumber(1)
     .then(() => {
       return logNumber(2);
     })
     .then(() => {
       return Promise.reject('failure, catch with catch');
     })
     .catch((err) => {
       console. log(err);
     })
     .then(() => {
         // catch returns a promise object, and the parameter is undefined, execute resolve
       return logNumber(3);
     })
     .finally(() => {
       return logNumber('Whether it succeeds or fails, it will show that the printing is complete');
     });
    

    Result: After executing .catch, the subsequent .then will be executed and 3 will be printed

    If an error is thrown after the catch: then the then after the catch will not be executed, and the promise chain will be interrupted

    .catch((err) => {
          console.log(err);
          throw new Error('error message')
        })
    

    Results:


    Note: usually put the catch at the end of all then to handle the failure. At this time, the then after the promise failure state will not continue to execute

    logNumber(1)
         .then(() => {
           return logNumber(2);
         })
         .then(() => {
           return Promise.reject('failure, catch with catch');
         })
         //3 will not print
         .then(() => {
           return logNumber(3);
         })
         .catch((err) => {
           console. log(err);
         })
    //Only print 1 2 and failure information
    


    3. Static method of Promise

    3.1 Promise. all(iterable)

    iterable: an iterable object, such as Array or String, Array, Map, Set all belong to the iterable type of ES6 This method returns a new promise, which succeeds only when all promises are successful and will form an array of the return values of all promises; when a promise state is reject, the new promise state is reject, and will Use the return value of the first reject as a parameter

    If the parameter contains non-promise values, these values will be ignored, but will still be placed in the return array (if promise.all succeeds)

    function logNumber(data, flag, delay) {
         return new Promise((resolve, reject) => {
           setTimeout(() => {
             if (flag) resolve(data);
             else reject('Failed');
           }, delay);
         });
       }
       let p1 = logNumber(1, true, 1000);
       let p2 = logNumber(2, true, 1000);
       //let p2 = logNumber(2,false,1000)
       //If p2 is changed to false, return failure
       let p3 = logNumber(3, true, 3000);
       Promise.all([p1, '123', p2, p3]).then(
         (res) => console. log(res),
         (err) => console. log(err),
       ); //return [1, '123', 2, 3] after 3 seconds
    

    Handwritten promise.all

    Promise. myAll = function (promises) {
         //The return result is a promise object
         return new Promise((resolve, reject) => {
           let result = [];
           promises. forEach((val) => {
             val.then(
    //If successful, store the result in an array, and when the number of successes is equal to the number of promises passed in, change the status of the returned promise to success and return the result
               (res) => {
                 result. push(res);
                 if (result. length === promises. length) {
                   resolve(result);
                 }
               },
               (err) => {
                 //If there is a failure, change the state to failure
                 reject(err);
               },
             );
           });
         });
       };
    


    3.2 Promise. allSettled(iterable)

    iterable: an iterable object, such as Array, where each member is a Promise
    This method will only have a final state when all Promises have results (whether they are fulfilled or rejected); returns a new Promise with a successful state and an array of objects, each representing the corresponding promise result (result contains status and corresponding value)
    It is usually used when you have multiple async tasks that do not depend on each other to complete successfully, or when you always want to know the result of each promise

    Handwritten promise.allSettled

    The idea is basically the same as the handwritten promise.all, except that the stored result should be an array object

    Promise. myAllSettled = function (promises) {
     //The return result is a promise object
     return new Promise((resolve, reject) => {
       let result = [];
       promises. forEach((val) => {
         val.then(
         //Store the state and value of each promise into an array
           (res) => {
             result.push({status:'fulfilled',value:res});
             if (result. length === promises. length) {
               resolve(result);
             }
           },
           (err) => {
              result.push({status:'rejected',value:err});
             if (result. length === promises. length) {
             resolve(result);
             }
           },
         );
       });
     });
    };
    


    3.3 Promise. any(iterable)

    Promise.any() takes an iterable of Promises, and as long as one of the promises succeeds, it returns the promise that has succeeded. If all promises fail, return a failed promise

    Handwritten promise.any

    Promise. myAny = function (promises) {
     //The return result is a promise object
     return new Promise((resolve, reject) => {
       const reasons = [];
       promises. forEach((promise) => {
         promise. then(
           (res) => resolve(res),
           (err) => {
             reasons. push(err);
             //If all statuses are failure, return failure function
             if (reasons. length === promises. length) {
               reject(new AggregateError(reasons));
             }
           },
         );
       });
     });
    };
    


    3.4 Promise. race(iterable)

    The Promise.race(iterable) method returns a promise. Once a promise in the iterator succeeds or rejects, the returned promise will succeed or reject, while any must return a successful.

    Promise. myRace = function (promises) {
     //The return result is a promise object
     return new Promise((resolve, reject) => {
       promises. forEach((promise) => {
         promise. then(
           (res) => resolve(res),
           (err) => reject(err),
         );
       });
     });
    };
    


    4. Promise extension method

    4.1 Implement a Promise scheduler with parallel constraints

    // Implement a Promise scheduler with parallel constraints
    
      function Scheduler(limit) {
        this.queue = [];
        this.maxCount = limit;
        this. runCounts = 0;
      }
      Scheduler.prototype.add = function (promiseCreator) {
        //promiseCreator is a promise instance object
        this.queue.push(promiseCreator);
      };
    
      Scheduler.prototype.request = function () {
        if (
          !this.queue||
          !this.queue.length||
          this.runCounts >= this.maxCount
        )
          return;
    
        this. runCounts++;
        //First in, first out, use shift to pop up
        this.queue
          .shift()()
          .then(() => {
            //Decrement the number of execution queues by 1, and resend the request
            this. runCounts--;
            this. request();
          });
      };
      Scheduler.prototype.taskStart = function () {
        //If i is less than the limit number, make a request
        for (let i = 0; i < this.maxCount; i++) {
          this. request();
        }
      };
    
    
    // use test
      const scheduler = new Scheduler(3);
    
      const addTask = (time, order) => {
        scheduler. add(function () {
          return new Promise((resolve) => {
            setTimeout(resolve, time);
          }).then(() => console.log(order));
        });
      };
    
      addTask(1000, '1');
      addTask(500, '2');
      addTask(300, '3');
      addTask(400, '4');
      scheduler. taskStart();
      //3 2 4 1
    


    4.2 Use Promise to realize asynchronous loading of pictures

    let asyncImg = (url) => {
        return new Promise((resolve,reject)=>{
            let img = new Image()
            img.src = url
            img.onload = () => {
                resolve(img);
            };
            img.onerror = (err) => {
            console.log(`failure, general operation failed here`);
            reject(err);
          };
        })
      }
    
    
    //Use: load multiple images asynchronously
         let imglist = ['../imgs/run1.jpg', '../imgs/run2.jpg', '../imgs/run3.jpg'];
         //The parameter imglist passed to Promise.all does not contain any promises, then an asynchronous completion is returned, and the return value is the passed array imglist
        imglist.forEach((imgsrc) => {
        // Pass the image path to the asynchronous loading function
        asyncImg(imgsrc).then((res) => {
          setTimeout(() => {
            document.body.append(res);
          }, 800);
        })
        .catch(err=>console.log(err))
      });
    
    // Load a picture asynchronously
      asyncImg('../imgs/run1.jpg')
        .then((res) => {
          console.log('loaded successfully');
          document.body.append(res)
        })
        .catch((error) => {
          console.log('Failed to load');
        });
    

    5. async and await

    5.1 Concept

    The emergence of async and await is to simplify the chain call of promise, making asynchronous code look more like synchronous code and easier to read

    async function return value:

    A Promise, the result of this promise object is determined by the return value of the async function execution: it will either be resolved by a value returned by the async function, or it will be rejected by an exception thrown from the async function

    async function foo() {
       console.log('foo function start');
       // 1. Return a value
       //return 2
       // 2. Return thenable
       return {
         then: function (resolve, reject) {
           reject('failed'); // capture with catch
         },
       };
    
       // 3. Return Promise
       // return new Promise((resolve, reject) => {
       // setTimeout(() => {
       // resolve("Successful promise")
       // }, 2000)
       // })
        // 4. The exception in the asynchronous function will be rejected as the Promise returned by the asynchronous function
      // throw new Error("error message")
    }
    // The return value of an asynchronous function must be a Promise
    const promise = foo();
    promise
       .then((res) => {console. log( res);})
       .catch((res) => console.log(res));
    


    await:

    The expression on the right side of await is usually a promise object, but it can also be other values
    If the expression is a promise object, await returns the promise success value If the expression is another value, directly use this value as the return value of await
    If the Promise returned by the expression after await is reject, then the result of this rejection will be directly used as the Promise of the function reject value

    PS:
    await must be written in the async function, but there can be no await in the async function

    The function body of an async function can be seen as separated by zero or more await expressions. The first line of code up to (and including) the first await expression (if any) runs synchronously. In this way, an async function without an await expression will run synchronously. However, if there is an await expression in the body of the function, the async function must be executed asynchronously

    As far as I understand, simply speaking, if the await keyword is not used in the async function, it will be treated as a normal function and executed in the order of js execution from top to bottom;
    If there is an await keyword in async, the statement after the first await expression will be put into the microtask queue for asynchronous execution, and when the second await area is reached, the process of the async function will be suspended again, and the awaited statement will continue put into microtask queue

    <script>
       console.log('synchronization task 1');
       async function test() {
         console. log(1);
        console. log(2);
         console. log(3);
       }
       let result = test();
       console.log('synchronization task 2');
    </script>
    

    Without await:

    console.log('synchronization task 1');
       async function test() {
         console. log(1);
         await console. log(2);
         console. log(3);
         await console. log(4);
       }
       let result = test();
       console.log('synchronization task 2');
    

    If await is used, the expression after the first await will be put into the microtask queue:


    5.2 Use async function to rewrite promise chain call

    Promise chain calls:

    function getData(url, data) {
         return new Promise((resolve, reject) => {
           var xhr = new XMLHttpRequest();
           xhr.open('GET', url);
           xhr.onload = function () {
             resolve(data + this. responseText);
           };
           xhr. send();
         });
       }
       getData(
         'http://www.filltext.com/?rows=1&fname={firstName}',
         'Start sending request',
       )
         .then((res) => {
           return getData(
             'http://www.filltext.com/?rows=1&lname={lastName}',
             res,
           );
         })
         .then((res) => {
           return getData(
             'http://www.filltext.com/?rows=1&company={business}',
             res,
           );
         })
         .then((res) => {
           console. log(res);
         })
         .catch((e) => console.log('Something went wrong:' + e));
    

    Rewrite with async and await (pass the result obtained each time as a parameter to the next request):

    async function asyncSend() {
     try {
       let res1 = await getData(
         'http://www.filltext.com/?rows=1&fname={firstName}',
         'Start sending request',
       );
       let res2 = await getData(
         'http://www.filltext.com/?rows=1&lname={lastName}',
         res1,
       );
       let res3 = await getData(
         'http://www.filltext.com/?rows=1&company={business}',
         res2,
       );
       console.log('final result:' + res3);
     } catch (e) {
       console.log('An error occurred:' + e);
     }
    }
       asyncSend();
    

    6. References

    Promise MDN
    async MDN

    Popular posts from this blog

    大学资料分享——广工计院各个科目实验报告、课设及期末复习资料!!

    Win10 远程计算机或设备将不接受连接

    JAVA Traffic Signal Light Course Design (Source Code + Report + Video Demonstration)

    Implementation of Ajax Interceptor