Diagram Promise Implementation Principle - Basic Implementation

This article was first published on vivo Internet Technology WeChat Public Number
Links: https://mp.weixin.qq.com/s/UNzYgpnKzmW6bAapYxnXRQ
Author: Kong Chuan Liang

When learning Promise, many students know it but don't know why. They don't understand its usage.This series of articles gradually implements Promise from shallow to deep, and demonstrates it with flowcharts, examples, and animations to gain a deep understanding of Promise usage.

This series of articles consists of the following chapters:

  1. Graphical Promise Implementation Principle (1) - Basic Implementation

  2. Diagram of Promise Implementation Principle (2) - Promise Chain Call

  3. Diagram of Promise Implementation Principle (3) - Implementation of Promise Prototype Method

  4. Graphical Promise Implementation Principle (4) - Promise Static Method Implementation

This article is suitable for people who know the use of Promise. If you are not sure, please refer to Mr. Ruan Yifeng's " Promise Object for ES6 Getting Started>.

There are many Promise specifications, such as Promise/A, Promise/B, Promise/D, and the upgraded version of Promise/A+ of Promise/A. It is interesting to know that the Promise/A+ specification was adopted in ES6.So the Promise source for this article is as follows Promise/A+ Specification To write (do not want to see the English version of the move) Promise/A+ Specification for Chinese Translation).

Introduction

In order to make it easier for everyone to understand, we start from a scene, think step by step, and it will be easier to understand.

Consider one of the following request processing for user id:

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

//Use 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.Otherwise, imagine if there are several dependent pre-requests that are asynchronous, and if there is no Promise at this time, the callback functions need to be nested layer by layer, which looks uncomfortable.The following:

//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
            })
        })
    })
});

//Use 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.Same as 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 end, Promise also uses callback functions, just encapsulating callbacks inside, using chained callbacks that have been called through the then method, making multilevel nesting of callbacks look the same, more intuitive and concise in writing and understanding.

1. Basic version

//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 second');
    }, 5000);
}).then((tip) => {
    console.log(tip);
})

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

  1. Calling the then method places onFulfilled, which you want to execute when the Promise asynchronous operation succeeds, in the callbacks queue, which is actually a registered callback function that you can think about in the direction of observer mode.

  2. The function passed in when the Promise instance is created is assigned a parameter of type function, Resolution, which receives a parameter value representing the result returned by the asynchronous operation. When the asynchronous operation succeeds, the resolve method is called, and what it actually does is to execute the callbacks in the callbacks queue one by one.

 

(Fig. Basic version implementation principle)

The function settings timer passed to Promise first simulates an asynchronous scenario, then calls the then method of the Promise object to register onFulfilled after the asynchronous operation completes, and finally, when the asynchronous operation completes, calls resolve(value) to execute the onFulfilled registered by the then method.

The onFulfilled registered by the then method is an array in which the then method can be called multiple times, and the registered onFulfilled is executed in the order in which it was added after the asynchronous operation is completed.The following:

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

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

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

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

//Minimalist 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 second');
    }, 5000);
}).then(tip => {
    console.log('then1', tip);
}).then(tip => {
    console.log('then2', tip);
});

(Figure: Chain call for the base version)

 

2. Join the Delay Mechanism

One problem with the implementation of Promise above is that if resolve executes before the then method registers onFulfilled, onFulfilled will not execute.For example, in the example above, we remove setTimout:

//resolve 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);
});

Execution results show that only "Synchronized Execution" is printed, and the following "then1" and "then2" are not printed.Looking back at Promise's source code, it's also easy to understand that when resolve executes, callbacks are empty arrays and have not yet been registered with onFulfilled.

This is obviously not allowed, and the Promises/A+ specification explicitly requires callbacks to be executed asynchronously to ensure a consistent and reliable execution sequence.So add some processing to ensure that the then method has registered all callbacks before resolve executes:

//Minimalist 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 the resolve and place the logic of the callback execution in the resolve at the end of the JS task queue through the setTimeout mechanism to ensure that the onFulfilled of the then method is registered when the resolve executes.

(Fig. Delay mechanism)

 

However, there are still problems. After resolve has been executed, onFulfilled registered via the then has no opportunity to execute.As shown below, then1 and then2 can be printed with a delay, but then3 in the example below still cannot be printed.So we need to increase the state and save the resolve value.

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. Increased status

To solve the problems thrown out in the previous section, we must incorporate a state mechanism, known as pending, fulfilled, rejected.

The Promises/A+ specification explicitly states that pending can be converted to fulfilled or rejected and can only be converted once, that is, if pending is converted to fulfilled, it cannot be converted to rejected.And fulfilled and rejected states can only be converted from pending, and they cannot be converted to each other.

This is what happens when the state is increased

//Minimalist implementation+Chain call+Delay mechanism+State
class Promise {
    callbacks = [];
    state = 'pending';//Increase status
    value = null;//Save results
    constructor(fn) {
        fn(this._resolve.bind(this));
    }
    then(onFulfilled) {
        if (this.state === 'pending') {//Before resolve, add 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 results
        this.callbacks.forEach(fn => fn(value));
    }
}

Note: When the state is added, the timer in the original_resolve can be removed.When reolve synchronizes execution, although callbacks are empty and callback functions have not been registered, that's okay, because when later registered, the callback will be immediately executed if the status is fulfilled.

(Figure: Promise Status Management)

Only fulfilled States and onFulfilled callbacks are added to the implementation source, but rejected and onRejected are added to the diagram for integrity.Later chapters will be implemented.

When resolve executes, it sets the state to fulfilled, saves the value of the value, and after that calls the new callback added by the n, it executes immediately, returning the saved value directly.

(Promise state change demo animation)

For more information, click: https://mp.weixin.qq.com/s/UNzYgpnKzmW6bAapYxnXRQ

So far, a functional Promise has been implemented, which implements then, chained calls, state management, and so on.But consider carefully that the implementation of a chain call simply returns this in the then, because the same instance can only return the same result if the call is made multiple times, which obviously does not meet our requirements.The next section describes how to implement a true chain call.

More please pay attention to vivo Internet technology WeChat Public Number

Note: For the article to be reproduced, please contact Microsignal: labs2020 first.

Keywords: Front-end

Added by jzhang1013 on Mon, 30 Mar 2020 06:00:03 +0300