In this paper, starting from vivo Internet technology WeChat public links: https://mp.weixin.qq.com/s/UNzYgpnKzmW6bAapYxnXRQ author: hole hanging light

Many students know the Promise but don’t know its reason and can’t understand its usage. This series of articles gradually realize Promise from simple to deep, and combined with flow charts, examples and animations to demonstrate, to achieve a deep understanding of the purpose of Promise usage.

This article series consists of the following chapters:

  1. Promise Implementation Principle (I) — Basic implementation

  2. (2) – The Promise chain call

  3. Graphic Promise implementation principle (three) – Promise prototype method implementation

  4. Schematic Promise implementation principle (four) – Promise static method implementation

This article is suitable for those who have some understanding of the use of Promise. If not, please refer to ruan Yifeng’s ES6 Introduction to Promise Objects.

There are many Promise specifications, such as Promise/A, Promise/B, Promise/D and the upgraded version of Promise/A Promise/A+. If you are interested, you can go to understand that the final ES6 uses the Promise/A+ specification. Therefore, the source code of this article is written in accordance with the Promise/A+ specification (do not want to see the English version of the move Promise/A+ specification Chinese translation).

primers

To make it easier for you to understand, let’s start with a scene and think step by step to make it easier to understand.

Consider the following request processing to get a user ID:

// Do not use Promise http.get('some_url'.function (result) {
    //dosomething console.log(result.id); }); // use Promise new Promise(function(resolve) {// Async request http.get('some_url'.function (result) {
        resolve(result.id)
    })
}).then(function (id) {
    //do something
    console.log(id);
})Copy the code


At first glance, it might seem simpler not to use Promise. This is not the case. If you have several dependent front-end requests that are asynchronous, it would look uncomfortable to have the callback function nested layer by layer without promises. 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) {
                //dosomething }) }) }) }); / / use the Promisefunction getUserId(url) {
    return new Promise(function(resolve) {// async request http.get(url,function (id) {
            resolve(id)
        })
    })
}
getUserId('some_url').then(function (id) {
    //do something
    returngetNameById(id); GetNameById is the same Promise wrapper as getUserId. Similarly hereinafter}). Then (function (name) {
    //do something
    return getCourseByName(name);
}).then(function (course) {
    //do something
    return getCourseDetailByCourse(course);
}).then(function (courseDetail) {
    //do something
});Copy the code


Realize the principle of

At the end of the day, Promise still uses callback functions, but with the callback wrapped inside, using a chain of calls through the then method to make the nested layers of callbacks look like one, making it more intuitive and concise to write and understand.

First, the basic version

// Minimal implementation 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 applicationlet p = new Promise(resolve => {
    setTimeout(() => {
        console.log('done');
        resolve('5');
    }, 5000);
}).then((tip) => {
    console.log(tip);
})Copy the code


The code above is very simple, and the general logic looks like this:

  1. Call the THEN method and place the Ondepressing that you want to perform when the Promise asynchronous operation succeeds into the Callbacks queue. In fact, this is the registration callback function. You can think in the direction of observer mode.

  2. When you create a Promise instance, the function passed in is given a parameter of type resolve, which takes a value that represents the result of the asynchronous operation. When the asynchronous operation succeeds, the resolve method is called, What you’re really doing is executing the callbacks in the Callbacks queue.


(Figure: Basic version implementation principle)

First, when the new Promise is completed, the function passed to the Promise sets the timer to simulate the asynchronous scenario. Then, the then method of the Promise object is called to register the ondepressing after the asynchronous operation is completed. Finally, when the asynchronous operation is completed, the resolve(value) is called. Execute the ondepressing registered with the THEN method.

The Ondepressing registered by the THEN method is stored in an array, so it can be seen that the THEN method can be called several times. Multiple ondepressing registered will be executed successively according to the order added after the asynchronous operation is completed. As follows:

//theninstructionslet p = new Promise(resolve => {
    setTimeout(() => {
        console.log('done');
        resolve('5');
    }, 5000);
});

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

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


In the example above, you define a variable p first, and then p.teng twice. The specification requires that the THEN method be able to be called chained.
The implementation is also simple, just return this in then.As follows:

// Simple implementation + chained call class Promise {callbacks = []; constructor(fn) { fn(this._resolve.bind(this)); }then(onFulfilled) {
        this.callbacks.push(onFulfilled);
        returnthis; } _resolve(value) {this.callbacks. ForEach (fn => fn(value)); }}let p = new Promise(resolve => {
    setTimeout(() => {
        console.log('done');
        resolve('5');
    }, 5000);
}).then(tip => {
    console.log('then1', tip);
}).then(tip => {
    console.log('then2', tip);
});Copy the code


(Figure: Base version of the chain call)

Add a delay mechanism

There is a problem with the Promise implementation above: If the resolve will be fulfilled before the THEN method registers the onFulfilled, the onFulfilled will not be fulfilled. For example, in the above example we removed setTimout:

// Resolve is synchronizedlet p = new Promise(resolve => {
    console.log('Synchronous execution');
    resolve('Synchronous execution');
}).then(tip => {
    console.log('then1', tip);
}).then(tip => {
    console.log('then2', tip);
});Copy the code

The result shows that only “synchronous execution” is printed, but “then1” and “then2” are not printed. Resolve is an empty array. Ondepressing has not yet been registered.

Promises/A+ specifies that callbacks should be performed asynchronously to ensure A consistent and reliable execution sequence. So add some processing to ensure that all callbacks are registered for the THEN method before resolve executes:

Class Promise {callbacks = []; constructor(fn) { fn(this._resolve.bind(this)); }then(onFulfilled) {
        this.callbacks.push(onFulfilled);
        return this;
    }
    _resolve(value) {
        setTimeout(() => {this.callbacks. ForEach (fn => fn(value)); }); }}Copy the code


Add a timer to Resolve. By using the setTimeout mechanism, place the logic executing the callback in Resolve to the end of the JS task queue to ensure that the ondepressing of the then method has been registered by the time resolve is executed.

(Figure: Delay mechanism)

However, there is still a problem. After the resolve is implemented, the onFulfilled registered through then will not have the chance to be executed. As shown below, then1 and then2 print after we add the delay, but then3 in this example still doesn’t print. So we need to increment 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); })});Copy the code


Add state

To solve the problem presented in the previous section, we must add a state mechanism, also known as pending, depressing, and Rejected.

Promises/A+ specification clearly states that pending can be changed to depressing or rejected and can only be changed once. That is to say, if pending is changed to the fulfilled state, then it cannot be changed to rejected again. Moreover, the depressing and Rejected states can only be converted by pending, and they cannot be converted to each other.

The implementation of the added state looks like this

// Simple implementation + chain call + delay mechanism + state class Promise {callbacks = []; state ='pending'; // Add status value = null; Constructor (fn) {fn(this._resolve.bind(this)); }then(onFulfilled) {
        if (this.state === 'pending'// Before resolve, add this. Callbacks. Push (ondepressing) to callbacks as before; }elseOndepressing (this.value); // This will be fulfilled soon. }return this;
    }
    _resolve(value) {
        this.state = 'fulfilled'; This. value = value; // save the result this.callbacks. ForEach (fn => fn(value)); }}Copy the code

Note: The _resolve timer can be removed after the state is added. When reolve synchronously executes, although the callbacks are empty and the callback function has not been registered, it does not matter, because the state will be judged as depressing when the callback is registered later, and the callback will be immediately executed.

(Figure: Promise state management)

Only the fulfilled state and onFulfilled callback are added in the source code, but rejected and onFulfilled are added in the schematic for completeness. This will be implemented in later sections.

The state will be fulfilled and the value will be saved. Any new callbacks added by then will be executed immediately and will return the saved value directly.

(Promise state change demo animation)

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

At this point, a rudimentary Promise is implemented, implementing THEN, chain calls, state management, and so on. However, the implementation of chained call only returns this in THEN. Since it is the same instance, calling THEN many times only returns the same result, which obviously cannot meet our requirements. In the next section, I’ll show you how to make a real chain call.

For more content, please pay attention to vivo Internet technology wechat public account

Note: To reprint the article, please contact our wechat account: LABs2020.