“This is the first day of my participation in the Gwen Challenge in November. See details of the event: The last Gwen Challenge in 2021”.


One, foreword

In the last article, the analysis and processing of various cases of returning value X to realize Promise mainly involve the following points:

  • The relevant Promise A+ specification was reviewed;
  • According to the specification description and requirements of Promise A+, the core resolution method is implemented: resolvePromise;

In this paper, we continued to improve the Promise and passed the promise-aplus-tests.


Two, questions about current promises

The current version of the Promise source code, has basically realized all the requirements in the Promise A+ specification;

In practice, however, the following two situations are still inconsistent with the native Promise:

  • 1. Resolve a Promise directly.
  • 2. The then method of a Promise returns a Promise, which in turn resolves a Promise.

note

  • Some people might say, “1 and 2 seem to be the same thing.” Resolve returns a promise;
  • First, let’s talk about the differences and then analyze them in detail:
    • The promise returned directly in resolve corresponds to the value in the source code;
    • The promise in resolve in the promise returned in then corresponds to y in the source code;

Resolve a Promise directly

1. Ask questions

If resolve is passed in as a Promise, what happens?

new Promise((resolve,reject)=>{
  resolve(new Promise((resolve,reject)=>{
    resolve(100)
  }))
}).then(data=>{
  console.log(data)
})
Copy the code

Resolve (100), then data in the successful callback is 100, so data is a promise, which is 100;

2. Test native Promises

The native Promise returns 100, as expected

3. Test handwritten promises

Returns a DULFILLED success Promise object that behaves differently from the original Promise:

Promise {
  state: 'DULFILLED'.value: 100.reason: undefined.onResolvedCallbacks: [].onRejectedCallbacks: []}Copy the code

4. Problem analysis

What’s wrong with the execution of handwritten promises versus native promises?

New Promise((resolve,reject)=>{resolve(new Promise((resolve,reject))=>{resolve(new Promise((resolve,reject))=>{// }). Then (data=>{// This data is a Pending state console.log(data)})Copy the code

Analyze the code execution process:

  • Create a promise1 instance with a new Promise, and the Executor1 executor function executes immediately.
  • Executor1, executedresolve(new Promise)Create an instance of promise2, and execute executor2 immediately.
  • With Executor2, there are no asynchronous operations, so execution continuesresolve(100);
  • In the resolve method, store value to 100, set the promise2 state to success, and execute a success callback (empty array not collected).
  • callthenMethods;
  • Promise source code processing: The THEN method creates promise3 internally. Then, promise2 becomes dulfulfilled successfully. Ondepressing is a successful callback, and promise2 is transferred to the then successful callback, that is, data, as data

This results in a DULFILLED Success promise object being returned;

Cause of the problem:

The source code does not take into account the possibility that a value in resolve(value) might be a promise;

    const reslove = (value) = >{
      if(this.state === PENDING){
        this.value = value // Value can be a promise
        this.state = DULFILLED;
        this.onResolvedCallbacks.forEach(fn= >fn())
      }
    }
Copy the code

Solution:

Add a judgment, if value is a promise, call then to make it perform;

5. Code implementation

Determine if value is a Promise (it must be its own Promise), call then, and pass resolve, reject (Success will invoke resolve; Reject is called on failure, returning the execution of the final promise;

const reslove = (value) = > { 
  // Value is a self-implemented Promise that calls then and returns the final result
  if(value instanceof Promise) {return value.then(reslove, reject)
  }

  if (this.state === PENDING) {
    this.value = value
    this.state = DULFILLED;
    this.onResolvedCallbacks.forEach(fn= > fn())
  }
}
Copy the code

In this way, when reslove makes a promise, its then method is called in the source reslove method, which returns the result of the promise execution.

Note: This situation is not explained in the Promise A+ specification;


Then return a promise, resolve a promise

1. Ask questions

In the previous article, when promise.then returns a Promise, the core logic resolvePromise looks like this:

function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError('Error occurred'))}if ((typeof x === 'object'&& x ! = =null) | |typeof x === 'function') {
    try {
      let then = x.then;
      if(typeof then === 'function') {// Call promise's then method
        then.call(x, y= > {
          resolve(y)     // Update the status of promise2
        }, r= > {
          reject(r)
        });
      }else{
        resolve(x)
      }
    } catch(e) { reject(e); }}else {
    resolve(x)
  }
}
Copy the code
  • The return value y of the method in then may be a promise, forming a nested promise;

Case 1 already dealt with the resolve promise case, is there still a problem here?

This is usually fine, but the promise here could be a promise that someone else fulfilled; (In case 1, the Promise must be self-implemented)

2. Test native Promises

let promise2 = new Promise((resolve, reject) = > {
  resolve(200)
}).then(data= > {
  return new Promise((resolve, reject) = > {
    setTimeout(() = >{
      resolve(new Promise((resolve, reject) = > {
        setTimeout(() = >{
          resolve(data)
        }, 1000)}})),1000)
  })
})
promise2.then(data= > {
  console.log(data)
})

/ / 200
Copy the code

3. Test handwritten promises

Promise {state: 'PENDING', value: undefined, reason: undefined, onResolvedCallbacks: [], onRejectedCallbacks: []}Copy the code

4. Problem analysis

let p = new Promise((resolve, reject) = > {
  // 1, the executor function is executed immediately
  resolve(200)
}).then(data= > {
  // 2, enter the successful callback processing, return Promise
  return new Promise((resolve, reject) = > {
    setTimeout(() = >{
      resolve(new Promise((resolve, reject) = > {
        setTimeout(() = >{
          resolve(data)
        }, 1000)}})),1000)
  })
})
p.then(data= > {
  console.log(data)
})
Copy the code

Analyze the code execution process:

  • The new Promise creates the instance, the executor executor function is executed immediately,resolve(200)Be performed;
  • The resolve method stores a value of 200, sets the Promise state to success, and executes a success callback (empty array not collected).
  • callthenMethods;
  • Promise source code processing: Then internally create the promise2-1 method. When P is dul-filled, use setTimeout to create macro task 1 (obtain the ondepressing return value x, and call resolvePromise to resolve the return value X). Deferred to the next event loop;
  • performp.then;
  • The state of the last macro task has not yet been executed, so the state of the last macro task is still PENDING, so the state of the last macro task is still PENDINGp.thenThe success/failure callback function in the
  • At this point, the code is done, that is, the first round of macro tasks is done;
  • Macro task 1 is executed, entering the successful processing of the first THEN in the code, data is 200, and the return value is a P

Promise;

  • Promise source code processing: internally get then return value x, call resolvePromise for unified parsing, because here x is a Promise, so, in resolvePromise will call its then method;
  • However, because the promise (x) has an asynchronous operation (setTimeout 1 second), when the then method of the promise (x) is called, the state of the promise (x) is still PENDING
  • One second later, execute resolve(new Promise), at which point the Promise in resolve is the y in the source code;

Question why

  • The hand-written source code deals only with the return value x of the method in then; But resolve is a promise; (Resolve promise is the y in the source code)
  • Since y is a promise, resolve(y), or data in THEN, yields a PENDING promise instance.

The solution

  • Since Y may be a promise object, resolvePromise is used for recursive processing of Y until y is an ordinary value.

Related Promise A+ specification contents:

5. Code implementation

function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError('Error occurred'))}if ((typeof x === 'object'&& x ! = =null) | |typeof x === 'function') {
    try {
      let then = x.then;
      if(typeof then === 'function'){
        then.call(x, y= > {
          // resolve(y)
          resolvePromise(promise2, y, resolve, reject) // handle y recursively
        }, r= > {
          reject(r)
        });
      }else{
        resolve(x)
      }
    } catch(e) { reject(e); }}else {
    resolve(x)
  }
}
Copy the code

Three, the compatible processing of Promise

In development, the promises used are not necessarily native or implemented by us;

It is possible for any framework/library/developer to implement A promise according to the Promise A+ specification;

To ensure that using these promises does not cause project problems, promises need to be handled in a compatible manner;

  • note

Theoretically, A Promise implemented strictly in accordance with the Promise A+ specification does not need compatibility processing; Therefore, compatibility processing is mainly used to make up for the Promise loophole and imrigor in implementation;

  • Such as:
let Promise1; // Self-fulfilling Promise
letPromise2;// A Promise that someone else fulfilled

promise = new Promise1((resolve, reject) = > {
  resolve(1);
}).then(() = > {
  return new Promise2();
})
Copy the code

Promise compatible processing, there are the following:

  • The state of each Promise instance can be changed only once;

4. Promise-aplus-tests

Promise-aplus-tests is used to test whether the promise implemented by oneself conforms to the Promise A+ specification.

1, install promise-aplus-tests

npm install promises-aplus-tests -g
Copy the code

2, add test entry – create delay object

  • Creating a delay objectPromise.deferred

Deferred object: an object that has a “deferred” effect (because it contains promises);

The promise.deferred method is used to test whether the Promise implementation on the current DFD object complies with the Promise A+ specification.

Promise.deferred = function(){
  let dfd = {}
  dfd.promise = new Promise((resolve,reject) = >{
    dfd.resolve = resolve;
    dfd.reject = reject;
  })
  return dfd;
}
Copy the code

The use of deferred objects reduces one layer of nesting;

  • inPromise.deferredMethod, you create a Promise objectdfd.promise;
  • Equivalent to binding resolve/reject todfd.resolve/dfd.reject;
  • The internal of the Deferred is called when a promise succeedsdfd.resolve; Failure to invokedfd.reject;

What it does: Migrates the Promise method to DFD objects, eliminating one layer of nesting through direct object access;

See an example of the effect of a delay object:

  • Do not use delay functions: need to nest a layernew Promise
function test() {
  return new Promise((resolve, reject) = > {
    setTimeout(() = > {
      resolve(200)},1000); })}Copy the code
  • Use delay functions: No nesting requirednew Promise
function test() {
  let dfd = Promise.deferred();
  setTimeout(() = > {
    dfd.resolve(200);
  }, 1000);
  return dfd.promise;
}
Copy the code

Conclusion:

  • Compared with the two methods of code, the use of delay functions significantly reduces the nesting of new Promises.
  • Each callPromise.deferred()You immediately get a brand new promise instance;
  • Deferred objects, which are “deferred” processing of deferred objects that contain promises;

3. Perform tests

Promises - Aplus - Tests XXX (Promise entry file path)Copy the code

Passed the Promise A+ specification for 872 test cases;


Five, the end

In this article, the source code of Promise was improved and tested through promise-aplus-tests. The main points are as follows:

  • Improve the Promise source code: support two nested Promise cases;
  • Analyze the Promise implementation process;
  • Create deferred objects and test with promise-aplus-tests;

Continue to implement promise.resolve and promise.reject;


Maintain a log

  • 20211102
    • Added instructions and examples for the “delay function section”;
    • Added support for two “nested return promises”;
    • Optimized the description of Promise execution process analysis;
    • Readjusted the first and second level contents, ending and abstract;
  • 20211115
    • Correct typos;