Graphical realization principle of promise (2) — chain call of promise

Time:2020-10-13

This article starts with WeChat official account of vivo Internet technology.
Link: https://mp.weixin.qq.com/s/Xz2bGaLxVL4xw1M2hb2nJQ
Author: morrain

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

1、 Preface

In the previous section, we implemented the basic version of promise:

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

However, chain calls only return this in the then method, so that promise instances can call the then method many times. However, because it is the same instance, calling then many times can only return the same result. Generally, the chain call we want is as follows:

//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);
}).then(function (name) {
    //do something
    return getCourseByName(name);
}).then(function (course) {
    //do something
    return getCourseDetailByCourse(course);
}).then(function (courseDetail) {
    //do something
});

Each then registered onfulfilled returns a different result, which is progressive. Obviously, return this cannot achieve this effect in the then method. Introduce real chain calls,Then must return a new promise instance.

Graphical realization principle of promise (2) -- chain call of promise

The real chain promise is to start the next promise after the current promise reaches the fulfilled state. So how can we connect the current promise with the next promise? (this is the difficulty in understanding promise. We will demonstrate the process through animation.).

2、 Implementation of chain call

Let’s take a look at the source code

//Complete implementation
class Promise {
    callbacks = [];
    State ='pending '; // increase state
    Value = null; // save the result
    constructor(fn) {
        fn(this._resolve.bind(this));
    }
    then(onFulfilled) {
        return new Promise(resolve => {
            this._handle({
                onFulfilled: onFulfilled || null,
                resolve: resolve
            });
        });
    }
    _handle(callback) {
        if (this.state === 'pending') {
            this.callbacks.push(callback);
            return;
        }
        //If nothing is passed in then
        if (!callback.onFulfilled) {
            callback.resolve(this.value);
            return;
        }
        var ret = callback.onFulfilled(this.value);
        callback.resolve(ret);
    }
    _resolve(value) {
        this.state  ='fulfilled'; // change state
        this.value  =Value; // save the result
        this.callbacks.forEach(callback => this._handle(callback));
    }
}

From the above implementation, we can see that:

  • In the then method, a new promise instance is created and returned, which is the basis of serial promise and the foundation of realizing real chain call.
  • The formal parameter onfulfilled passed in by the then method and the resolve passed in when creating a new promise instance are put together and pushed into the callbacks queue of the current promise. This is the key to connect the current promise with the next promise.
  • According to the specification, onfulfilled can be empty. When it is empty, onfulfilled is not called.

Take a look at the animation:

Graphical realization principle of promise (2) -- chain call of promise

(promise chain call demo animation)

When the first promise succeeds, the resolve method sets its state to fulfilled and saves the value brought by resolve. Then, the object in callbacks is taken out and the onfulfilled of the current promise is executed. The return value is passed to the second promise by calling the resolve method of the second promise. The animation is as follows:

Graphical realization principle of promise (2) -- chain call of promise

(promise chained call fulfilled)

In order to really see the chain call process, I write a mockajax function to simulate asynchronous requests

/**
 *Simulate asynchronous request
 *The URL requested by @ param {*} URL
 *@ param {*} s specifies the time taken for the request, that is, how long after the request will return. Unit second
 *Callback function after @ param {*} callback request
 */
const mockAjax = (url, s, callback) => {
    setTimeout(() => {
        Callback (URL + 'asynchronous request time' + S +'seconds');
    }, 1000 * s)
}

In addition, I added log output to promise’s source code and added a construction order identifier to clearly see the construction and execution process

//Demo1
new Promise(resolve => {
  mockAjax('getUserId', 1, function (result) {
    resolve(result);
  })
}).then(result => {
  console.log(result);
})

[source code of Demo1]

The results are as follows:

[Promse-1]:constructor
[Promse-1]:then
[Promse-2]:constructor
[Promse-1]:_handle state= pending
[Promse-1]:_handle callbacks= [ { onFulfilled: [Function], resolve: [Function] } ]
=> Promise { callbacks: [], name: 'Promse-2', state: 'pending', value: null }
[Promse-1]:_resolve
[Promse-1]:_ Resolve value = getuserid asynchronous request took 1 second
[Promse-1]:_handle state= fulfilled
Getuserid asynchronous request took 1 second
[Promse-2]:_resolve
[Promse-2]:_resolve value= undefined

Through the printed log, you can see:

  1. Construct promise-1 instance and immediately execute mackajax (‘getuserid ‘, callback);
  2. Call the then method of promise-1 and register the onfulfilled function of promise-1.
  3. A new promise instance is constructed inside the then function: promise-2. Execute promise-1’s_ Handle method.
  4. Promise-1 is still in the pending state.
  5. Promise-1._ In handle, on fulfilled registered in promise-1 and resolve in promise-2 are saved in callback in promise-1.
  6. At this point, the execution of the current thread ends. Return a promise instance of promise-2.
  7. After 1 s, the asynchronous request returns. To change the state and result of promise-1, execute resolve (result).
  8. The value of promise-1 is changed, and the content is the result returned by the asynchronous request: “getuserid asynchronous request takes 1 s”.
  9. The state of promise-1 becomes fulfilled.
  10. Promise-1’s onfulfilled is executed and “getuserid asynchronous request takes 1 second” is printed out.
  11. Then call promise-2. Resolve.
  12. Change the value and status of promise-2. Because promise-1’s onfulfilled has no return value, the value of promise-2 is undefined.

In the above example, what would happen if the asynchronous request was changed to synchronous?

new Promise(resolve => {
  Resolve ('getuserid synchronization request ');
}).then(result => {
    console.log(result);
});

//Print log
[Promse-1]:constructor
[Promse-1]:_resolve
[Promse-1]:_ Resolve value = getuserid synchronization request
[Promse-1]:then
[Promse-2]:constructor
[Promse-1]:_handle state= fulfilled
Getuserid synchronization request
[Promse-2]:_resolve
[Promse-2]:_resolve value= undefined
=> Promise {
  callbacks: [],
  name: 'Promse-2',
  state: 'fulfilled',
  value: undefined }

If you are interested, you can analyze it yourself.

3、 The real meaning of chain call

When the onfulfilled of the current promise is executed, the return value is passed to the second promise as the value of the second promise by calling the resolve method of the second promise. So we consider the following Demo:

//Demo2
new Promise(resolve => {
    mockAjax('getUserId', 1, function (result) {
        resolve(result);
    })
}).then(result => {
    console.log(result);
    //Process the result in the first layer
    Let exresult = 'prefix:' + result;
    return exResult;
}).then(exResult => {
    console.log(exResult);
});

[source code of demo2]

We add a layer of then to see the result of execution

[Promse-1]:constructor
[Promse-1]:then
[Promse-2]:constructor
[Promse-1]:_handle state= pending
[Promse-1]:_handle callbacks= [ { onFulfilled: [Function], resolve: [Function] } ]
[Promse-2]:then
[Promse-3]:constructor
[Promse-2]:_handle state= pending
[Promse-2]:_handle callbacks= [ { onFulfilled: [Function], resolve: [Function] } ]
=> Promise { callbacks: [], name: 'Promse-3', state: 'pending', value: null }
[Promse-1]:_resolve
[Promse-1]:_ Resolve value = getuserid asynchronous request took 1 second
[Promse-1]:_handle state= fulfilled
Getuserid asynchronous request took 1 second
[Promse-2]:_resolve
[Promse-2]:_resolve value= 前缀:Getuserid asynchronous request took 1 second
[Promse-2]:_handle state= fulfilled
前缀:Getuserid asynchronous request took 1 second
[Promse-3]:_resolve
[Promse-3]:_resolve value= undefined:

Chain calls can be written infinitely, and the value of onfulfilled return at the upper level will become the result of onfulfilled at the next level. You can refer to demo3:

[source code of demo3]

It is easy to find that only the first request in demo3 is asynchronous, and the latter is synchronous. There is no need for such a chained implementation. As follows, we can get the three results we want: the values printed out separately.

//Equivalent to demo3
new Promise(resolve => {
    mockAjax('getUserId', 1, function (result) {
        resolve(result);
    })
}).then(result => {
    console.log(result);
    //Process the result in the first layer
    Let exresult = 'prefix:' + result;
    console.log(exResult);

    Let finalresult = exresult + ': suffix';
    console.log(finalResult);
});

What’s the real meaning of chain calls?

What we just demonstrated is when the return value of onfulfilled is value. What if it is a promise? If it is, the developer who uses promise can determine the status of subsequent promise through onfulfilled.

So in the_ Add the judgment of the previous promise onfulfilled return value in resolve

_resolve(value) {

        if (value && (typeof value === 'object' || typeof value === 'function')) {
            var then = value.then;
            if (typeof then === 'function') {
                then.call(value, this._resolve.bind(this));
                return;
            }
        }

        this.state  ='fulfilled'; // change state
        this.value  =Value; // save the result
        this.callbacks.forEach(callback => this._handle(callback));
    }

In terms of code,It makes a special judgment on the value in resolve to determine whether the value of resolve is a promise instance. If it is a promise instance, the state change interface of the current promise instance will be re registered in the onfulfilled of promise corresponding to the value of resolve. That is to say, the state of the current promise instance depends on the promise of the resolve value The state of the instance.

Graphical realization principle of promise (2) -- chain call of promise

//Demo4
const pUserId = new Promise(resolve => {
  mockAjax('getUserId', 1, function (result) {
    resolve(result);
  })
})
const pUserName = new Promise(resolve => {
  mockAjax('getUserName', 2, function (result) {
    resolve(result);
  })
})

pUserId.then(id => {
  console.log(id)
  return pUserName
}).then(name => {
  console.log(name)
})

[source code of demo 4]

The results of implementation are as follows:

[Promse-1]:constructor
[Promse-2]:constructor
[Promse-1]:then
[Promse-3]:constructor
[Promse-1]:_handle state= pending
[Promse-1]:_handle callbacks= [ { onFulfilled: [Function], resolve: [Function] } ]
[Promse-3]:then
[Promse-4]:constructor
[Promse-3]:_handle state= pending
[Promse-3]:_handle callbacks= [ { onFulfilled: [Function], resolve: [Function] } ]
=> Promise { callbacks: [], name: 'Promse-4', state: 'pending', value: null }
[Promse-1]:_resolve
[Promse-1]:_ Resolve value = getuserid asynchronous request took 1 second
[Promse-1]:_handle state= fulfilled
Getuserid asynchronous request took 1 second
[Promse-3]:_resolve
[Promse-3]:_resolve value= Promise { callbacks: [], name: 'Promse-2', state: 'pending', value: null }
[Promse-2]:then
[Promse-5]:constructor
[Promse-2]:_handle state= pending
[Promse-2]:_handle callbacks= [ { onFulfilled: [Function], resolve: [Function] } ]
[Promse-2]:_resolve
[Promse-2]:_ Resolve value = GetUserName asynchronous request took 2 seconds
[Promse-2]:_handle state= fulfilled
[Promse-3]:_resolve
[Promse-3]:_ Resolve value = GetUserName asynchronous request took 2 seconds
[Promse-3]:_handle state= fulfilled
GetUserName asynchronous request took 2 seconds
[Promse-4]:_resolve
[Promse-4]:_resolve value= undefined
[Promse-5]:_resolve
[Promse-5]:_resolve value= undefined

Similarly, I made a demo animation to restore the process:

Graphical realization principle of promise (2) -- chain call of promise

(promise real chain call)

So far, the whole content of promise chain call is realized. Chain call is the difficulty and the key point of promise. We must have a deep understanding through examples and animation. The next section describes the implementation of other promise prototypes.

Please pay attention to more detailsVivo Internet technologyWeChat official account

Graphical realization principle of promise (2) -- chain call of promise

Note: please contact the wechat:Labs2020Contact.