preface

Last week the package covered the basic implementation of promises. This article focuses on the core part of promises — the Promise Resolution Procedure.

At the beginning of this article, I’ll discuss the package’s understanding of the resolvePromise function.

In the Basics section we dealt with the basic Promise functionality, asynchronous logic, and chained calls, which we did like this:

The then method is divided into three cases according to the promise state. In each case, the return value x after the callback function is executed is directly used as the success value of the promise.

This should make you wonder, do all types of return x return directly?

Promises/A+ Promises/A+ Promises/A+ Promises/A+ Promises/A+ Promises/A+ Promises/A+

We can also find from the implementation code of the basic part that the processing codes of the three cases are highly similar. If the processing of multiple cases of return value X is added, the code will be unimaginable. Therefore, we extract this part of logic into resolvePromise method.

The resolvePromise method accepts four parameters:

resolvePromise(promise2, x, resolve, reject);
Copy the code

Promise2 is the return value of the THEN method, x is the return value of the then callback, and we call resolve or Reject based on the return value of x.

The return value of x

Promises/A+ specification Before reading Promises/A+ specification, let’s think about what happens to return x: Promises/A+ specification

  • xIs a common value (primitive type)
  • xIs based onPromises/A+specificationpromiseobject
  • xIs based on other specificationspromiseobject
  • xispromise2(x points to the same object as promise2)

Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+

Promises/A+ Code Interpretation

Promises/A+ : Promises/A+

Let’s understand what the specification says.

The Promise resolution process is an abstract operation with parameter values Promise and value, which we express as [[Resolve]](Promise,x)

This is the same as the resolvePromise mentioned above, except that we pass in both the Resovle and Reject methods of Promise2 as parameters for coding purposes.

If x is thenable, then we think of X as a promise-like object that tries to make promises adopt x’s state. As long as they comply with Promises/A+ ‘s THEN method, which allows Promise to interoperate with Thenable’s processing.

Promises/A+ is just one of them. Promises/A+ wants to make Promises that are compatible with other Promises based on whether they are thenable or not.

  1. ifpromisexReference to the same objectTypeErrorReasons to reject (to avoid circular references)

Under what circumstances can this phenomenon occur? Look at this chestnut:

const p = new Promise((resolve, reject) = > {
  resolve(1);
});
const promise2 = p.then((data) = > {
  return promise2;
});
// TypeError: Chaining cycle detected for promise #<Promise>
promise2.then(null.(err) = > console.log(err));
Copy the code
  1. ifxIs apromise, take its state
    • ifxThe state ispending,promiseAlso need to maintainpendingState until thexState tofulfilledrejected
    • ifxThe state isfulfilled, completes with the same valuepromise
    • ifxThe state isrejectedAnd refused for the same reasonpromise

The specification says what to do when x is a Promise, but it doesn’t say how to tell if x is a Promise

  1. xIt’s an object or a function
    • The statementthenThe value ofx.then
    • If I retrieve the propertyx.thenCauses an exception to be throwne, theeReject for reasons of rejectionPromise.
    • ifthenPhi is a function,xAs athenthisThis method is called with a successful callback as the first argument and a failed callback as the second argumentMinimum judgment to determine whether it is a promise
      • If successful callback to valueyCall, run[[Resolve]](promise,y)
      • If failure callback to causerCall,rRefused topromise
      • If both successful and failed callbacks are called or the same parameter is called more than once, the first call takes precedence and all other calls are ignored.
      • If the callthenMethod throws an exceptione:
        • If both successful and failed callbacks are called, ignore them
        • Not called, usedeReject as a causepromise
    • ifthenIt’s not a functionxComplete as a valuepromise
  2. ifxIs neither an object nor a functionxComplete as a valuepromise

We have finished the interpretation of the specification, and the following questions are put forward to deepen your understanding of the Promise Resolution.

The problem

  1. retrievex.thenProperties can have exceptions, can you give an example like chestnuts?

Because Promises/A+ are compatible with other Promises with Thenable capability, let’s imagine A scenario like this: Promises/A+ Promises

// Someone is very perverted
// It sets the getter for the then property of object X to an error
const x = {};
Object.defineProperty(x, "then", {
  get() {
    throw Error("cant execute then function"); }});If x. Chen is called, an exception will be thrown
// Uncaught Error: cant execute then function
x.then;
Copy the code
  1. whythenMethods bycallCall instead ofx.thenCall?

The then attribute has been successfully retrieved, and there is some risk if retrieved again. Once again, the chestnut, let’s change it a little bit:

let n = 0;
const x = {};
Object.defineProperty(x, "then", {
  get() {
    // Change to throw an exception on the second call
    n++;
    if (n >= 2) {
      throw Error("cant execute then function");
    }
    return "success"; }});// success
x.then;
// Uncaught Error: cant execute then function
x.then;
Copy the code

Then. Call (x) has the same effect as x. Teng, and reduces the risk of secondary retrieval by using then. Call (x).

  1. If successful callback to valueyCall, run[[Resolve]](promise, y)What does this rule mean?

After thinking for a long time, Xiao Bao finally realized this pity. He began to mistakenly think that this criterion is targeted at two ondepressing situations:

  • onFulfilledThe function returnsPromiseThe instance
  • onFulfilledThe function is executed with argumentsPromsieInstance.

But in the second case, by contrast, it is impossible to achieve this specification at all. Xiao Bao was still very curious about the second one, so he went to read the Promises/A+ specification again and again. He found that Promises/A+ specification failed to make it through the net. There was no mention of this in the specification. But I tried it in the browser and found that for the second case, ES6 also recursively resolves the case (I’ll write a separate article comparing the two cases when I get the chance).

This is a big pity that will return a Promise.

const p1 = new Promise((resolve, reject) = > {
  resolve("i am p1");
});

const p2 = new Promise((resolve, reject) = > {
  resolve("i am p2");
});
const p3 = p2.then((res) = > {
  console.log(res);
  return p1;
});

/ / {
// [[Prototype]]: Promise
// [[PromiseState]]: "fulfilled"
// [[PromiseResult]]: "i am p1"
// }
console.log(p3);
// false
console.log(p3 === p1);
Copy the code

As you can see from the output, P2’s then method returns p1, which returns a new Promise instance, different from P1 but in the P1 state.

  1. If both successful and failed callbacks are called or the same parameter is called more than once, the first call takes precedence and all other calls are ignored. What does this specification deal with?

You might be surprised to see this specification, because our handwritten Promise dealt with the current situation in the base article, and the success or failure callback would only call one or the other. Promises/A+ specification Promises many times to be compatible with other Promises that have Thenable capability. Promise instances implemented by other specifications may not handle this situation. Therefore, this specification is intended to be compatible with other imperfect promise implementations.

The source code to achieve

A circular reference

If a promise and X reference the same object, it is rejected for TypeError reasons. Therefore, we need to add a step verification to the resolvePromise:

const resolvePromise = function (promise, x, resolve, reject) {
  // Loop references and wait for them to finish
  if (promise === x) {
    // Terminate the promise with a type error
    return reject(
      new TypeError(
        `TypeError: Chaining cycle detected for promise #<myPromise> `)); }};Copy the code

Determine if X is a Promise instance

Based on the interpretation of the above specification, we can summarize the following steps to determine whether X is a Promise instance:

  1. promiseThe instance should be an object or a function: decide firstxWhether it is an object or function
  2. promiseObjects must havethenableAbility: Continue retrievalx.thenattribute
  3. thenProperty should be an executable function: final judgmentthenIs it a function (this is the minimum judgment)
  4. If all of the above are met,Promises/A+Just thinkxIs apromiseThe instance

To refine: first determine whether x is an object or a function; Then check whether x. teng is a function

Let’s write this part of the code:

function resolvePromise(promise, x, resolve, reject) {
  // Determine whether x is an object (excluding null cases) or a function
  if ((typeof x === "object"&& x ! = =null) | |typeof x === "function") {
    // Retrieving x.teng may throw an exception
    try {
      / / retrieve x.t hen
      let then = x.then;
      // Check if then is a function
      // This is the minimum judgment. If this condition is satisfied, the promise instance is assumed
      if (typeof then === "function") {
        // executing x.teng retrieves the THEN attribute again, risking an error
        // The other two parameters will be explained later
        then.call(x);
      } else {
        // then method is not a function, a normal value -- {then:123}resolve(x); }}catch (e) {
      Reject (e)reject(e); }}else {
    Resolve (x); resolve(x)resolve(x); }}Copy the code

The return value x is Promise

We have explained this specification in detail above, but how to implement this specification? As simple as that, we just need to make a few changes to then.call(x).

then.call(
  x,
  (y) = > {
    // If x is a promise, use its state to decide whether to succeed or fail
    resolvePromise(promise, y, resolve, reject); // Recursively resolve y
  },
  (r) = > {
    // The result of the failure is no longer parsedreject(r); });Copy the code

I don’t know if you understand the recursion, right? Small bag give chestnuts.

// If y is a promise, let's take the following example
const y = new Promise((resolve, reject) = > {
  resolve(1);
});

/ / after resolvePromise
resolvePromise(promise, y, resolve, reject);
Copy the code

ResolvePromise (Promise, Y, resolve, Reject) executes like this:

  1. After a series of judgments, finally passedy.thenIs determined by the functionypromise
  2. performthen(y, resolvePromsie, rejectPromise)
  3. The above code is equivalent to executing the following code
y.then(
  (y1) = > {
    resolvePromise(promise, y1, resolve, reject);
  },
  (r1) = >{ reject(r1); });Copy the code
  1. y1The value is1, is a normal value, so call directlyresolve(y1)
  2. So it’s implementedpromise2Take the return valuexThe state of the

Compatibility with imperfect promise implementations

To be compatible with imperfect promise implementations, we need to add a lock to the resolvePromise implementation.

function resolvePromise(promise, x, resolve, reject) {
  // Determine whether x is an object (excluding null cases) or a function
  let called = false;
  if ((typeof x === "object"&& x ! = =null) | |typeof x === "function") {
    try {
      let then = x.then;
      if (typeof then === "function") {
        then.call(
          x,
          (y) = > {
            // Add a lock to avoid execution failure
            if (called) return;
            called = true;
            resolvePromise(promise, y, resolve, reject);
          },
          (r) = > {
            // Add a lock to avoid successful execution after a failure
            if (called) return;
            called = true; reject(r); }); }else{ resolve(x); }}catch (e) {
      // Add a lock to avoid successful execution after a failure
      if (called) return;
      called = true; reject(e); }}else {
    Resolve (x); resolve(x)resolve(x); }}Copy the code

Then method modification

At the beginning of this article, we mentioned that the code for the three cases of then method is repeated too much. We extract this part into resolvePromise, and the first parameter is promise2.

We took the then method part of the base article and focused on the resolvePromise invocation part. Promise2 is not accessible until the whole thing is executed, resolvePromise is not accessible at this point.

then(onFulfilled, onRejected) {
  let promise2 = new Promise((resolve, reject) = > {
    if (this.status === FULFILLED) {
      try {
        let x = onFulfilled(this.value);
        resolvePromise(promise2, x, resolve, reject);
      } catch(e) { reject(e); }}});return promise2;
}
Copy the code

Therefore, we need to add an asynchronous operation to the resolvePromise. This script uses setTimeout to implement it.

then(onFulfilled, onRejected) {
  let promise2 = new Promise((resolve, reject) = > {
    if (this.status === FULFILLED) {
      // When resolvePromise is executed, the promise2 object can be obtained
      setTimeout(() = > {
        try {
          let x = onFulfilled(this.value);
          resolvePromise(promise2, x, resolve, reject);
        } catch(e) { reject(e); }},0)}});return promise2;
}
Copy the code

This is the end of handwritten Promises. Let’s test whether handwritten Promises can pass the case test offered by Promises/A+.

Full version promise code: Handwritten full version promise

Test case

Delay object

Add the deferred part of the code to our handwritten Promise:

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

promises-aplus-tests

Use NPM to install Promises – aplus-Tests.

npm i promises-aplus-tests
Copy the code

Then go to the promise folder to test and execute

promises-aplus-tests promise.js
Copy the code

Pass the test, and you’re done!

After the language

I am battlefield small bag, a fast growing small front end, I hope to progress together with you.

If you like xiaobao, you can pay attention to me in nuggets, and you can also pay attention to my small public number – Xiaobao learning front end.

All the way to the future!!

An early end to the epidemic will restore peace to the world