In this paper, starting from vivo Internet technology WeChat public links: https://mp.weixin.qq.com/s/Xz2bGaLxVL4xw1M2hb2nJQ author: Morrain

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

One, foreword

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

// 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


However, the chained call only returns this in the THEN method, so that the Promise instance can call the THEN method multiple times, but since it is the same instance, calling the THEN method multiple times will only return the same result. Generally, we want the chained call to look like this:

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


Each ondepressing registered with THEN returns different results, step by step. It is obvious that return this cannot achieve this effect in the THEN method. Introducing true chain calls,
Then must return a new Promise instance.

A true chain Promise is fulfilled after the current Promise reaches the fulfilled state, the next Promise will be fulfilled. So how do we bridge current and subsequent promises? (This is the hard part about understanding promises, and we’ll illustrate this with animations.)

Second, the implementation of chain call

First look at the implementation source code:

// Full implementation of class Promise {callbacks = []; state ='pending'; // Add status value = null; 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; } / / ifthenNothing is passed inif(! callback.onFulfilled) { callback.resolve(this.value);return;
        }
        var ret = callback.onFulfilled(this.value);
        callback.resolve(ret);
    }
    _resolve(value) {
        this.state = 'fulfilled'; This. value = value; // save the result this.callbacks. ForEach (callback => this._handle(callback)); }}Copy the code


From the above implementation, we can see:

  • In the THEN method, a new Promise instance is created and returned, which is the basis for serial promises and is the basis for true chained calls.

  • The onFulfilled parameter passed in to the then method, together with the Resolve passed in when creating a new Promise instance, is pushed into the callbacks queue of the current Promise, This is the key to bridging current and subsequent promises.

  • According to the specification, ondepressing can be empty, and ondepressing will not be called when it is empty.

Take a look at the animation:



(Promise chain call demo animation)

When the first Promise succeeds, the resolve method sets its state to fulfilled and saves the value that resolve brings. Then the object in the Callbacks is removed, the onFulfilled Promise of the current Promise is performed, and the return value is passed to the second Promise by calling the second Promise’s resolve method. The animation is shown below:



This is a big Promise.

To really see the chain call process, I write a mockAjax function that simulates asynchronous requests:

/** * emulate asynchronous request * @param {*} url Url of the request * @param {*} s specifies how long it will take for the request to return. */ const mockAjax = (url, s, callback) => {mockAjax = (url, s, callback);setTimeout(() => {
        callback(url + 'Asynchronous request Time' + s + '秒');
    }, 1000 * s)
}Copy the code


In addition, I added log output to the source code of Promise and added the construction order identifier, so that we can clearly see the construction and execution process:

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



Not the source of】

The result is 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 duration: 1 second [promse-1]:_handle state= depressing GetUserId Asynchronous request takes 1 second [promse-2]:_resolve [promse-2]:_resolve value= undefinedCopy the code


From the printed log, you can see:

  1. Construct a promise-1 instance and execute mackAjax(‘getUserId’,callback) immediately;

  2. Call the THEN method of Promise-1 to register the ondepressing function of Promise-1.

  3. Inside the then function, a new Promise instance is constructed: promise-2. Execute the _handle method of Promise-1 immediately.

  4. Promise-1 is still in the pending state.

  5. The ondepressing and resolve registered in PROMISe-1 and promise-2 will be saved in the callbacks inside promise-1 in the promise-1._handle.

  6. At this point, the current thread completes execution. The Promise instance of PROMISe-2 is returned.

  7. After 1s, the asynchronous request returns. To change the status and result of PROMISe-1, execute resolve(result).

  8. The value of Promise-1 is changed to the result returned by the asynchronous request: “getUserId Asynchronous request takes 1s”.

  9. The state of promise-1 becomes a big pity.

  10. The ondepressing of Promise-1 is performed, and the print out “getUserId Asynchronous request takes 1 second “.

  11. Then call promise-2.resolve.

  12. Change the value and state of PROMISe-2. Because the ondepressing of Promise-1 does not return a value, the value of promise-2 is undefined.

In the example above, what happens if you change an asynchronous request to a synchronous one?

new Promise(resolve => {
  resolve('getUserId Synchronization request '); }).then(result => { console.log(result); }); [promse-1]:constructor [promse-1]:_resolve [promse-1]:_resolve value= getUserId Synchronize request [promse-1]:then[promse-2]:constructor [promse-1]:_handle state= depressing [promse-2]:_resolve [promse-2]:_resolve value= undefined => Promise { callbacks: [], name:'Promse-2',
  state: 'fulfilled',
  value: undefined }Copy the code


If you’re interested, you can analyze it for yourself.

The real meaning of chain call

When the onFulfilled Promise of the current Promise is fulfilled, the return value is passed to the second Promise as the value of the second Promise by calling the second Promise’s resolve method. So let’s consider the following Demo:

//Demo2
new Promise(resolve => {
    mockAjax('getUserId', 1, function(result) { resolve(result); }) }).then(result => { console.log(result); // Perform the first layer of processing on resultlet exResult = 'the prefix:' + result;
    return exResult;
}).then(exResult => {
    console.log(exResult);
});Copy the code



Demo2 source】

We add a layer of then and see what happens:

[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 duration: 1 second [promse-1]:_handle state= depressing GetUserId Asynchronous request takes 1 second [promse-2]:_resolve [promse-2]:_resolve value= Prefix :getUserId Asynchronous request takes 1 second [promse-2]:_handle state= [promse-3]:_resolve [promse-3]:_resolve value= undefined: this is a big pity.Copy the code


The chain call can be written indefinitely, and the value of the ondepressing return of the next stage will become the result of the next ondepressing. See Demo3:

[Demo3]

It is easy to see that in Demo3 above, only the first request is asynchronous, and the rest is synchronous, so there is no need for such a chained implementation. The three results we want are as follows: the values printed separately.

// Demo3 new Promise(resolve => {mockAjax('getUserId', 1, function(result) { resolve(result); }) }).then(result => { console.log(result); // Perform the first layer of processing on resultlet exResult = 'the prefix:' + result;
    console.log(exResult);

    let finalResult = exResult + ': the suffix';
    console.log(finalResult);
});Copy the code



So what’s the real point of chain calls?

This is a big pity. The onFulfilled return value is “value”. What if it is a Promise? Perhaps through onFulfilled, the developer who uses Promise can decide the state of subsequent Promise.

Thus, add judgment to the return value of the previous Promise ondepressing 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'; This. value = value; // save the result this.callbacks. ForEach (callback => this._handle(callback)); }Copy the code


From the code,
It makes a special determination on the value of resolve to determine whether the value of resolve is an instance of a Promise. If it is, Then the state change interface of the current Promise instance will be re-registered into the ondepressing of the Promise corresponding to the resolve value, that is, the state of the current Promise instance will depend on the state of the Promise instance of the resolve value.

//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)
})Copy the code



Demo 4 source code】

The result is 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 duration: 1 second [promse-1]:_handle state= depressing GetUserId Asynchronous request takes 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 takes 2 seconds. [promse-2]:_handle state= depressing [promse-3]:_resolve [promse-3]:_resolve value= getUserName Asynchronous request takes 2 seconds. [promse-3]:_handle state= depressing GetUserName Asynchronous request takes 2 seconds [promse-4]:_resolve [promse-4]:_resolve value= undefined [promse-5]:_resolve [promse-5]:_resolve value= undefinedCopy the code


Again, I made a demo animation that recreates the process:



(Real chain call of Promise)

At this point, all of the Promise chain calls are implemented. The chain call is the difficulty and the focus of Promise. Be sure to see it through examples and animations. The next section covers the implementation of other prototype methods for Promise.

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

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