Schematic realization principle of promise (1) — basic realization

Time:2020-7-5

This article starts with WeChat official account of vivo Internet technology.
Link: https://mp.weixin.qq.com/s/UNzYgpnKzmW6bAapYxnXRQ
Author: Kong Chui Liang

Many students are learning promise, but they don’t know why. They can’t understand the usage of promise.This series of articles from simple to deep gradually realize promise, and combined with flow chart, examples and animation to demonstrate, to achieve a profound understanding of the purpose of promise usage.

This series of articles consists of the following chapters:

  1. Schematic realization principle of promise (1) — basic realization
  2. Graphical realization principle of promise (2) — chain call of promise
  3. Graphic realization principle of promise (3) — Realization of promise prototype method
  4. Graphic realization principle of promise (4) — Realization of promise static method

This article is suitable for those who know the usage of promise. If it is not clear, please refer to the promise object of ES6 introduction by Ruan Yifeng.

There are many promise specifications, such as promise / A, promise / B, promise / D and promise / A +. Those who are interested can learn about it. Finally, the promise / A + specification is adopted in ES6. Therefore, the promise source code of this paper is compiled according to promise / A + specification (do not want to see the English version of the step-by-step promise / A + specification Chinese translation).

Introduction

In order to make it easier for everyone to understand, we start from a scene and follow the thinking step by step to make it easier to understand.

Consider the following request processing to obtain the user ID:

//Do not use promise        
http.get('some_url', function (result) {
    //do something
    console.log(result.id);
});

//Using promise
new Promise(function (resolve) {
    //Asynchronous request
    http.get('some_url', function (result) {
        resolve(result.id)
    })
}).then(function (id) {
    //do something
    console.log(id);
})

At first glance, it seems simpler not to use promise. In fact, it is not. Imagine that if several dependent front requests are asynchronous, and if there is no promise, the callback functions will be nested layer by layer, which seems very uncomfortable. As follows:

//Do not use promise        
http.get('some_url', function (id) {
    //do something
    http.get('getNameById', id, function (name) {
        //do something
        http.get('getCourseByName', name, function (course) {
            //dong something
            http.get('getCourseDetailByCourse', function (courseDetail) {
                //do something
            })
        })
    })
});

//Using promise
function getUserId(url) {
    return new Promise(function (resolve) {
        //Asynchronous request
        http.get(url, function (id) {
            resolve(id)
        })
    })
}
getUserId('some_url').then(function (id) {
    //do something
    Return getnamebyid (ID); // getnamebyid is the same promise encapsulation as getuserid. The same below
}).then(function (name) {
    //do something
    return getCourseByName(name);
}).then(function (course) {
    //do something
    return getCourseDetailByCourse(course);
}).then(function (courseDetail) {
    //do something
});

Implementation principle

In the final analysis, promise also uses callback functions. It just encapsulates callbacks in the interior. It has been used through the chain call of then method, which makes the multi-layer callback nesting look like the same layer. It will be more intuitive and concise in writing and understanding.

1、 Basic version

//The realization of minimalism
class Promise {
    callbacks = [];
    constructor(fn) {
        fn(this._resolve.bind(this));
    }
    then(onFulfilled) {
        this.callbacks.push(onFulfilled);
    }
    _resolve(value) {
        this.callbacks.forEach(fn => fn(value));
    }
}

//Promise application
let p = new Promise(resolve => {
    setTimeout(() => {
        console.log('done');
        Resolve ('5 seconds');
    }, 5000);
}).then((tip) => {
    console.log(tip);
})

The above code is very simple, and the general logic is as follows:

  1. Call the then method and put the onfulfilled to the callbacks queue when promise asynchronous operation is successful. In fact, it is to register a callback function. You can think in the direction of observer mode;
  2. When creating promise instance, the function passed in will be given a function type parameter, that is, resolve. It receives a parameter value, which represents the result returned by the asynchronous operation. When the asynchronous operation is successfully executed, the resolve method will be called. In this case, the real operation is to execute the callbacks in the callbacks queue one by one.

Schematic realization principle of promise (1) -- basic realization

(Figure: implementation principle of basic version)

First, when the new promise is completed, the function passed to promise sets a timer to simulate the asynchronous scene. Then, it calls the then method of promise object to register onfulfilled after the asynchronous operation is completed. Finally, when the asynchronous operation is completed, resolve (value) is called to execute onfulfilled registered by the then method.

The onfulfilled registered by the then method exists in an array. It can be seen that the then method can be called multiple times. Multiple onfulfilled registered by the then method will be executed successively according to the order of addition after the asynchronous operation is completed. As follows:

//Description of then
let p = new Promise(resolve => {
    setTimeout(() => {
        console.log('done');
        Resolve ('5 seconds');
    }, 5000);
});

p.then(tip => {
    console.log('then1', tip);
});

p.then(tip => {
    console.log('then2', tip);
});

In the above example, we need to define a variable p first, then P. then twice. The specification requires that the then method should be able to be called chained.The implementation is also simple. You only need to return this in then.As follows:

//Simple implementation + Chain call
class Promise {
    callbacks = [];
    constructor(fn) {
        fn(this._resolve.bind(this));
    }
    then(onFulfilled) {
        this.callbacks.push(onFulfilled);
        Return this; // look here
    }
    _resolve(value) {
        this.callbacks.forEach(fn => fn(value));
    }
}

let p = new Promise(resolve => {
    setTimeout(() => {
        console.log('done');
        Resolve ('5 seconds');
    }, 5000);
}).then(tip => {
    console.log('then1', tip);
}).then(tip => {
    console.log('then2', tip);
});

Schematic realization principle of promise (1) -- basic realization

(Figure: chain call of basic version)

2、 Add delay mechanism

There is a problem with the implementation of promise above: if resolve is executed before the then method registers onfulfilled, onfulfilled will not be executed. For example, in the above example, we remove settimout:

//Resolve was executed synchronously
let p = new Promise(resolve => {
    console.log ('synchronous execution ');
    Resolve ('synchronous execution ');
}).then(tip => {
    console.log('then1', tip);
}).then(tip => {
    console.log('then2', tip);
});

The execution results show that only “synchronous execution” is printed, and neither “then1” nor “then2” are printed. If you go back to the source code of promise, it’s easy to understand. When resolve is executed, callbacks are still empty arrays and have not been registered on onfulfilled.

This is obviously not allowed. The promises / A + specification explicitly requires that callbacks should be executed asynchronously to ensure a consistent and reliable execution sequence. Therefore, some processing should be added to ensure that the then method has registered all callbacks before the resolve execution

//Simple implementation + Chain call + delay mechanism
class Promise {
    callbacks = [];
    constructor(fn) {
        fn(this._resolve.bind(this));
    }
    then(onFulfilled) {
        this.callbacks.push(onFulfilled);
        return this;
    }
    _resolve(value) {
        SetTimeout (() = > {// look here
            this.callbacks.forEach(fn => fn(value));
        });
    }
}

Add a timer to resolve. Through the setTimeout mechanism, the logic of executing callback in resolve is placed at the end of JS task queue to ensure that onfulfilled of then method has been registered when resolve is executed.

Schematic realization principle of promise (1) -- basic realization

(Figure: delay mechanism)

However, there is still a problem. After the resolve is executed, the onfulfilled registered through then has no chance to execute. As shown below, after adding delay, then1 and then2 can be printed, but then3 in the following example still cannot be printed. So we need to increase the state and save the value of resolve.

let p = new Promise(resolve => {
    console.log ('synchronous execution ');
    Resolve ('synchronous execution ');
}).then(tip => {
    console.log('then1', tip);
}).then(tip => {
    console.log('then2', tip);
});

setTimeout(() => {
    p.then(tip => {
        console.log('then3', tip);
    })
});

3、 Increase status

In order to solve the problems raised in the previous section, we must add state mechanisms, which are known as pending, fulfilled, rejected.

The proposals / A + specification clearly stipulates that pending can be converted to either fulfilled or rejected and can only be converted once. That is to say, if pending is converted to the completed state, it cannot be converted to rejected again. Moreover, the states of fulfilled and rejected can only be transformed from pending, and they cannot be converted to each other.

Schematic realization principle of promise (1) -- basic realization

The implementation after adding state is like this

//Minimalist implementation + Chain call + delay mechanism + state
class Promise {
    callbacks = [];
    State ='pending '; // increase state
    Value = null; // save the result
    constructor(fn) {
        fn(this._resolve.bind(this));
    }
    then(onFulfilled) {
        if ( this.state  ==='pending') {// before resolve, add it to callbacks as before
            this.callbacks.push(onFulfilled);
        }Else {// after resolve, the callback is executed directly and the result is returned
            onFulfilled(this.value);
        }
        return this;
    }
    _resolve(value) {
        this.state  ='fulfilled'; // change state
        this.value  =Value; // save the result
        this.callbacks.forEach(fn => fn(value));
    }
}

Note: after adding the status, the original_ The timer in resolve can be removed. When reolve is executed synchronously, although the callbacks are empty and the callback function has not been registered, it does not matter because when the callback function is registered later, the callback will be executed immediately if it is judged that the status is full.

Schematic realization principle of promise (1) -- basic realizationSchematic realization principle of promise (1) -- basic realization

(Figure: promise state management)

In the implementation source code, only the status of fulfilled and the callback of onfulfilled are added, but for the sake of integrity, rejected and onrejected are added in the schematic diagram. It will be implemented in later chapters.

When resolve executes, it sets the state to fulfilled and stores the value of value. After that, calling the new callback added by then will execute immediately and return the saved value value directly.

(promise state change animation)

For details, please click: https://mp.weixin.qq.com/s/UNzYgpnKzmW6bAapYxnXRQ

At this point, we have realized the function of chain management. But think about it carefully, the implementation of chain call only returns this in then, because it is the same instance. If you call then many times, you can only return the same result, which obviously can’t meet our requirements. The next section describes how to implement a real chained call.

Please pay attention to more detailsVivo Internet technologyWeChat official account

Schematic realization principle of promise (1) -- basic realization

Note: please contact the wechat:labs2020Contact.

Recommended Today

ASP.NET Example of core MVC getting the parameters of the request

preface An HTTP request is a standard IO operation. The request is I, which is the input; the responsive o is the output. Any web development framework is actually doing these two things Accept the request and parse to get the parameters Render according to the parameters and output the response content So let’s learn […]