Deep understanding and implementation of promise and async/await

Promise and async/await have been put forward for a long time. Many people know that both of them are to solve the problem of callback to hell. Many interviewers in the interview like to compare them. Let me talk about the difference between promise and async/await and the simple principle realization. Avoid the embarrassment of interview!

It's quite long. I understand and implement the two parts (mainly in the implementation). Recently, I saw that many interviewees have mentioned the problems of handwritten promise and async/await, so I sorted them out at will. I also sorted them out before. call, apply, bind and manual implementation)

promise

Promise, in short, is a container in which a certain time (usually the result of an asynchronous operation) that will end in the future is stored. Asynchronous is realized by chain call synchronization.

Characteristic:

  • The state of the object is not affected by the outside world;
  • Chain call (still not escaping the callback problem)
  • Exception handling (catch)
  • Once the state changes, it will not change again and the state will solidify.

You can pay attention to the specific Six things you may not know about Promise as well as Talk about Promise's pit

Promise/A + implementation

This article Analyze the internal structure of Promise Write very good, here also share to you, in the interview to write a very simple can, for the source code we can understand;

Requirements for implementing Promise:

  • To construct a project instance, you need to pass in a function to the project constructor. The passed in function needs two parameters, namely resolve and reject. Note that both parameters are function type parameters.
  • There is also the then method (multiple then can form a chain call) on the project. The then method is used to specify the state change of the Promsie object to determine the operation to be performed. The first function (onFulfilled) is executed when resolving, and the second function (onRejected) is executed when rejecting.
  • When the state changes to resolve, it can't be changed to reject. Otherwise, it is the same.

Based on the above requirements, we can implement a simple Promise:

// promise three states
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

class myPromise {
    constructor(executor) {
        this.status = PENDING; // Declare the initial state;
        this.value = undefined; // Information returned when the status is fulfilled
        this.reason = undefined; // Reason for rejection in rejected state;
        this.onFulfilledCallbacks = []; // Store the onFulfilled function corresponding to the fulfilled state
        this.onRejectedCallbacks = []; // Store the onRejected function corresponding to the rejected state
        //=>Execute Excutor
        let resolve = result => { // resolve method
            if (this.status !== PENDING) return;

            // Why resolve plus setTimeout?
            // 2.2.4 the specification onFulfilled and onRejected can only run when the execution context stack only contains platform code.
            // The platform code here refers to the implementation code of engine, environment and promise. In practice, ensure that the onFulfilled and onRejected methods are executed asynchronously, and should be executed in the new execution stack after the event cycle that the then method is called.

            setTimeout(() => {
                //Only by the pending state = > fulfilled state (avoid calling resolve reject multiple times)
                this.status = FULFILLED;
                this.value = result;
                this.onFulfilledCallbacks.forEach(cb => cb(this.value));
            }, 0);
        };
        let reject = reason => { // reject method
            if (this.status !== PENDING) return;
            setTimeout(() => {
                this.status = REJECTED;
                this.reason = reason;
                this.onRejectedCallbacks.forEach(cb => cb(this.reason))
            })
        };
        // Catch exceptions thrown in the executor executor
        try {
            executor(resolveFn, rejectFn);
        }
        catch (err) {
            //=>Exception information is handled according to rejected status
            reject(err);
        }
    }

    // Add. then method
    then(onFullfilled, onRejected) {
        this.onFulfilledCallbacks.push(onFullfilled);
        this.onRejectedCallbacks.push(onRejected);
    }

    // Add. catch method
    catch(onRejected) {
        return this.then(null, onRejected);
    }
}

module.exports = myPromise;

But in the interview, some people will ask to write a Promises/A + specification The perfect Promise for.

First, let's look at the Promises specification:

There are many Promise specifications, such as Promise/A, Promise/B, Promise/D and the upgraded Promise/A + of Promise/A. The Promise/A + specification is adopted in ES6.

// promise three states
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

class myPromise {
    constructor(executor) {
        this.status = PENDING; // Declare the initial state;
        this.value = undefined; // Information returned when the status is fulfilled
        this.reason = undefined; // Reason for rejection in rejected state;
        this.onFulfilledCallbacks = []; // Store the onFulfilled function corresponding to the fulfilled state
        this.onRejectedCallbacks = []; // Store the onRejected function corresponding to the rejected state
        //=>Execute Excutor
        let resolve = result => { // resolve method
            if (this.status !== PENDING) return;

            // Why resolve plus setTimeout?
            // 2.2.4 the specification onFulfilled and onRejected can only run when the execution context stack only contains platform code.
            // The platform code here refers to the implementation code of engine, environment and promise. In practice, ensure that the onFulfilled and onRejected methods are executed asynchronously, and should be executed in the new execution stack after the event cycle that the then method is called.

            setTimeout(() => {
                //Only by the pending state = > fulfilled state (avoid calling resolve reject multiple times)
                this.status = FULFILLED;
                this.value = result;
                this.onFulfilledCallbacks.forEach(cb => cb(this.value));
            }, 0);
        };
        let reject = reason => { // reject method
            if (this.status !== PENDING) return;
            setTimeout(() => {
                this.status = REJECTED;
                this.reason = reason;
                this.onRejectedCallbacks.forEach(cb => cb(this.reason))
            })
        };
        // Catch exceptions thrown in the executor executor
        try {
            executor(resolveFn, rejectFn);
        }
        catch (err) {
            //=>Exception information is handled according to rejected status
            reject(err);
        }
    }

    // Add. then method
    then(onFullfilled, onRejected) {
        onFullfilled = typeof onFullfilled === "function" ? onFullfilled : value => value;
        onRejected = typeof onRejected === "function" ? onRejected : reason => {
            throw reason;
        };
        switch (self.status) {
            case PENDING:
                // Staging onFulfilled/onRejected collection to collection when resolve/rejected is called asynchronously
                return promise2 = new myPromise((resolve, reject) => {
                    this.onFulfilledCallbacks.push(()=>{
                        try {
                            let x = onFullfilled(this.value);
                            this.resolvePromise(promise2, x, resolve, reject); // Return value of one onFulfilled on the new promise resolve
                        }
                        catch (e) {
                            reject(e); // Catch the exception then(onFulfilled, onRejected) thrown in the previous onFulfilled;
                        }
                    });
                    this.onRejectedCallbacks.push(() => {
                        try {
                            let x = onRejected(this.reason);
                            this.resolvePromise(promise2, x, resolve, reject);
                        }
                        catch (e) {
                            reject(e); // error catch
                        }
                    });
                });
                break;
            case FULFILLED:
                return promise2 = new myPromise(function (resolve, reject) {
                    try {
                        let x = onFullfilled(this.value);
                        //Pass the methods in the last then to the next Promise state
                        this.resolvePromise(promise2, x, resolve, reject);
                    }
                    catch (e) {
                        reject(e);//error catch
                    }
                });
                break;
            case REJECTED:
                return promise2 = new myPromise(function (resolve, reject) {
                    try {
                        let x = onRejected(this.reason);
                        //Pass the methods in then to the state of the next Promise
                        this.resolvePromise(promise2, x, resolve, reject);
                    }
                    catch (e) {
                        reject(e);
                    }
                });
                break;
            default:
        }
    }

    // Add. catch method
    catch(onRejected) {
        return this.then(null, onRejected);
    }

    static deferred() { // Delay object
        let defer = {};
        defer.promise = new myPromise((resolve, reject) => {
            defer.resolve = resolve;
            defer.reject = reject;
        });
        return defer;
    }

    static all(promises = []) {
        let index = 0,
            result = [];
        return new Promise((resolve, reject) => {
            for (let i = 0; i < promises.length; i++) {
                promises[i].then(val => {
                    index++;
                    result[i] = val;
                    if (index === promises.length) {
                        resolve(result)
                    }
                }, reject);
            }
        })
    }
    
    static resolvePromise(promise, x, resolve, reject) {
        if (promise === x) { // If the x returned from onFulfilled is promise2, it will cause a circular reference error.
            throw new TypeError("type error")
        }
        let isUsed;
        if (x !== null && (x instanceof Object || x instanceof Function)) {
            try {
                let then = x.then;
                if (typeof then === "function") {
                    //It's a promise situation.
                    then.call(x,(y) => {
                        if (isUsed) return;
                        isUsed = true;
                        this.resolvePromise(promise, y, resolve, reject);
                    },(e) => {
                        if (isUsed) return;
                        isUsed = true;
                        reject(e);
                    })
                }
                else {
                    //It's just a function or an object.
                    resolve(x)
                }
            }
            catch (e) {
                if (isUsed) return;
                isUsed = true;
                reject(e);
            }
        }
        else {
            //Basic type returned, resolve directly
            resolve(x)
        }
    }
}

module.exports = myPromise;

Promise test

npm i -g promises-aplus-tests

promises-aplus-tests Promise.js

The principle of promise the implementation of Promise/A + is over.

async/await

  • async/await is more semantic. Async is the abbreviation of "asynchronous". async function is used to declare that a function is asynchronous. Await can be considered as the abbreviation of async wait, which is used to wait for the execution of an asynchronous method to complete.
  • async/await is a solution to asynchronous problems with synchronous thinking (the code will continue to execute after the results come out)
  • The traditional callback nesting can be replaced by the synchronous writing method of multi-layer async function, avoiding the chain call of Promise, and the code looks simple and clear.

Chestnut:

function sleep(wait) {
    return new Promise((res,rej) => {
        setTimeout(() => {
            res(wait);
        },wait);
    });
}

async function demo() {
    let result01 = await sleep(100);
    //The next sentence will not be executed until the last await is executed
    let result02 = await sleep(result01 + 100);
    let result03 = await sleep(result02 + 100);
    // console.log(result03);
    return result03;
}

demo().then(result => {
    console.log(result);
});

The highest level of asynchronous programming doesn't care about its asynchrony at all. Many people think it is the ultimate solution of asynchronous operation. But there is no substitute for Promise, because async/await is parasitic on Promise. Syntax sugar for the Generator.

You can have a look at the students who are still unfamiliar with Generator. generator written by Liao Xuefeng , I won't introduce it too much here.

Implement a simple async/await

The async/await syntax sugar operates using the Generator function + auto actuator. We can refer to the following examples

// A promise is defined to simulate asynchronous requests. The purpose is to pass in parameters++
function getNum(num){
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(num+1)
        }, 1000)
    })
}

//Automatic executor, if a Generator function is not completed, it is called recursively.
function asyncFun(func){
  var gen = func();

  function next(data){
    var result = gen.next(data);
    if (result.done) return result.value;
    result.value.then(function(data){
      next(data);
    });
  }

  next();
}

// For the Generator function to be executed, the internal data will be called after the promise of the next step is completed.
var func = function* (){
  var f1 = yield getNum(1);
  var f2 = yield getNum(f1);
  console.log(f2) ;
};
asyncFun(func);

In the process of execution, judge whether the promise of a function is completed. If it is, pass the result to the next function and continue to repeat this step.

End

If you find any problems in the process, please put forward in time.

Keywords: Javascript npm Programming

Added by jf3000 on Thu, 24 Oct 2019 06:04:31 +0300