Promise solves pain points like “callback hell” and “trust issues” compared to callback functions, and greatly improves the readability of the code. In modern front-end development, promises have become almost the preferred way to handle async (though there are more convenient async/await, escape). This article starts with the idea of promises and how they work, digs into each API, and finally writes A Promise that follows the Promise/A+ specification.

asynchronous

There are six asynchronous JavaScript methods.

  • Event listeners

  • The callback function

  • Publish/subscribe

  • Promise

  • The generator

  • async/await

The callback function

If you’re asked in an interview what’s wrong with callback functions, you’ll probably answer callback hell without thinking. Indeed, callback hell can occur when we need to send multiple asynchronous requests and each request needs to depend on each other.

Some time ago I wrote a weather micro channel small program Natsuha, its logic to obtain the weather is roughly as follows (of course, the real scene is much more complex).

  • First get the user’s latitude and longitude (interface A)

  • Query cities by latitude and longitude (interface B)

  • Get the corresponding weather information according to the city (interface C)

To handle this logic as a callback, it would look something like this:

ajax(A, () => {
  // Get latitude and longitude
  ajax(B, () => {
    // Check cities by latitude and longitude
    ajax(C, () => {
      // Get weather information by city
    });
  });
});
Copy the code

It looks ugly, doesn’t it? I believe you are familiar with the shortcomings of the callback function, so I won’t expand it here, but summarize it.

  • The writing sequence of code logic is inconsistent with the execution sequence, which is not conducive to reading and maintenance.

  • Large-scale code refactoring is required when the order of asynchronous operations changes.

  • Callbacks are basically anonymous functions, making it difficult to track bugs.

  • Callback functions are called by third-party library code (Ajax in the example above) rather than their own business code, resulting in inversion of control (IoC).

Briefly talking about inversion of control, JavaScript you Don’t Know (Middle Volume) puts the biggest drawback of callback functions down to a trust issue. In this case ajax is a three-way function (think of it as jQuery’s $.ajax()), and we leave our business logic, the callback function, to Ajax to handle. But Ajax is just a black box for us, and if Ajax itself is flawed, our callback function is in danger, which is called a “trust issue.”

Promise addresses these shortcomings by reversing control and then reversing control. That way, instead of passing on our program to a third party, we can let that third party give us the ability to know when its task is over, and let our own code decide what to do next.

What is a Promise

JavaScript You Don’t Know (Middle Volume) gives an example:

I ordered a hamburger at a fast food restaurant and paid $1.07 for it. This means THAT I made a request for a value (hamburger).

The cashier then hands me a receipt, which guarantees that I will eventually get the burger, so the receipt is a promise.

While I’m waiting for my meal, I can do something else, like check Twitter to see how many more stars 996. Icu got today. The reason I can do something else is because the receipt represents my future burger. It has become something of a placeholder for the burger. Essentially, this placeholder makes the value not time dependent, but a future value.

Finally, I heard the waiter calling for number 250, and I was able to exchange the receipt for my burger.

But there could have been another outcome. When I went to pick up my meal, the waiter apologetically told me that the burger was sold out. In addition to anger, we can also see that the future value may succeed or fail.

Promise Basics

The Promise lifecycle

Eg: Each Promise goes through a short life cycle in eg: first it is in progress, when an operation is not completed, so it is also not processed in eg; Once the asynchronous operation is finished and the Promise becomes settled, it will enter one of two states:

  • The asynchronous operation is Fulfilled successfully

  • Rejected: The asynchronous operation fails to complete successfully due to a program error or other reasons

Promise constructor

The Promise itself is a constructor that takes a function called Executor, which is passed as arguments to two functions called resolve() and reject(). Resolve () is called when the executor succeeds, while reject() is called when the executor operation fails. Look at the following example.

const fs = require('fs');

const promise = path= >
  // The actuator accepts resolve() and reject() as arguments
  new Promise((resolve, reject) = > {
    fs.readFile(__dirname + '/' + path, 'utf-8', (err, data) => {
      if (err) {
        Call reject() on failure
        reject(err);
        return;
      }
      // Call resolve() on success
      resolve(data);
    });
  });
Copy the code

Promise’s then method

The then() method takes two functions as arguments, the first as a callback on completion and the second as a callback on rejection. Both parameters are optional, so you can listen only for completion or only for rejection. When the first argument is null and the second argument is a callback, it means listen rejection. In practice, both completion and rejection should be monitored.

const promise = new Promise((resolve, reject) = > {
  resolve('success');
});

// Listen complete and reject
promise.then(
  res= > {
    / / finish
    console.log(res);
  },
  e => {
    / / deny
    console.log(e); });// Only listen to complete
promise.then(res= > {
  console.log(res);
});

// If the first argument is null, it means rejection
promise.then(null, res => {
  / / finish
  console.log(res);
});
Copy the code

Two other Promise methods are catch(), which listens for rejection, and finally(), which is executed regardless of success or failure. Chained calls are obviously more readable, so we recommend the following.

promise
  .then(res= > {
    console.log(res);
  })
  .catch(e= > {
    console.log(e);
  })
  .finally((a)= > {
    console.log('Execute this in spite of success or failure.');
  });
Copy the code

Promise chain call

A new Promise is created and returned each time the then() or catch() methods are called, and the next Promise is resolved only after the current Promise is completed or rejected.

In the following example, p.teng () returns a second Promise after completion, and then calls its then() method, meaning that the second then() method will be called only after the first Promise has been resolved.

let p = new Promise((resolve, reject) = > {
  resolve(42);
});

p.then(value= > {
  console.log(value); / / 42
}).then((a)= > {
  console.log('Can be executed to'); // 'execute to'
});
Copy the code

Take the above example apart and it looks like this. The result of calling P1.then () is stored in P2, and p2.then() is called to add the final THEN ().

let p1 = new Promise((resolve, reject) = > {
  resolve(42);
});

let p2 = p1.then(value= > {
  console.log(value);
});

p2.then((a)= > {
  console.log('Can be executed to');
});
Copy the code

Let’s look at a chain call with an example. Here’s a scenario for getting city weather: We first need to call the getCity interface to get the city ID, followed by getWeatherById/ city ID to get the weather information for the city. Start by wrapping a native Ajax with Promise. (Type on the blackboard; the interview may require handwriting)

const getJSON = function(url) {
  const promise = new Promise(function(resolve, reject) {
    const handler = function() {
      if (this.readyState ! = =4) {
        return;
      }
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText)); }};const client = new XMLHttpRequest();
    client.open('GET', url);
    client.onreadystatechange = handler;
    client.responseType = 'json';
    client.setRequestHeader('Accept'.'application/json');
    client.send();
  });

  return promise;
};

const baseUrl = 'https://5cb322936ce9ce00145bf070.mockapi.io/api/v1';
Copy the code

Request data through chain calls, and finally catch errors.

getJSON(`${baseUrl}/getCity`)
  .then(value= > getJSON(`${baseUrl}/getWeatherById/${value.cityId}`))
  .then(value= > console.log(value))
  .catch(e= > {
    console.log(e);
  });
Copy the code

Capture the error

When the then() or catch() methods throw an error, the catch() method in the next Promise of the chain call can receive the error via catch(). On the side, exceptions don’t have to occur only in promises, they can also occur in then() or catch().

let p1 = new Promise((resolve, reject) = > {
  resolve(42);
});

p1.then(value= > {
  throw new Error('then()' error ');
}).catch(e= > {
  console.log(e.message); // 'then()' error '
});
Copy the code

Not only can then() throw an exception, but catch() can also throw an exception that can be caught by the next catch(). Therefore, you should always leave a catch() at the end of the Promise chain to ensure that all possible errors are handled correctly. Look at the following example.

let p1 = new Promise((resolve, reject) = > {
  throw new Error('Actuator error');
});

p1.catch(e= > {
  console.log(e.message); // 'executor error'
  throw new Error('catch()' error ');
}).catch(e= > {
  console.log(e.message); // 'catch()' error '
});
Copy the code

The return value of the Promise chain

An important feature of the Promise chain is the ability to pass data from one Promise to the next, passing data down the chain by completing the return value of the handler. Let’s look at the following example.

function task() {
  return new Promise((resolve, reject) = > {
    setTimeout((a)= > {
      resolve('task');
    }, 1000);
  });
}

task()
  .then(res= > {
    console.log(res);
    return 'taskB';
  })
  .then(res= > {
    console.log(res);
    return 'taskC';
  })
  .then(res= > {
    console.log(res);
    throw new Error(a); }) .catch(e= > {
    console.log(e);
    return 'taskD';
  })
  .then(res= > {
    console.log(res);
  });
Copy the code

The running result is shown in the figure above. We know that each call to THEN () or catch() returns a new Promise instance, and by specifying the return value of the handler, we can continue passing data along the chain.

So the first then() passes ‘taskB’ as an argument to the next then(), and the second then() passes ‘taskC’ as an argument to the third then().

The third then() throws an exception, which must be caught by a later rejection handler, so the catch() will print an error from the previous THEN ().

Don’t forget that catch() returns ‘taskD’ which can also be caught by the last then().

Other construction methods

Promise. Resolve () and Promise. Reject ()

Promise.resolve() and promise.reject () are similar shortcuts to create a completed or rejected Promise. In addition, promise.resolve () can accept as an argument the non-promise thenable, which is an object that owns the then method.

// p1 and p2 are equivalent
const p1 = new Promise((resolve, reject) = > {
  reject('Oops');
});

const p2 = Promise.reject('Oops');

// p3 and p4 are equivalent
const p3 = new Promise((resolve, reject) = > {
  resolve('Oops');
});

const p4 = Promise.resolve('Oops');
Copy the code

For promise.resolve (), it can also accept a non-promise thenable as an argument. It can create either a completed Promise or a Promise to reject.

let thenable1 = {
  then(resolve, reject) {
    resolve(1); }};let p1 = Promise.resolve(thenable1);

p1.then(value= > console.log(value)); / / 1

let thenable2 = {
  then(resolve, reject) {
    reject(1); }};let p2 = Promise.resolve(thenable2);

p2.catch(reason= > console.log(reason)); / / 1
Copy the code

Promise.all()

This method takes a single iteration object (most commonly an array) as an argument and returns a Promise. The elements of this iterable are all promises, and the returned promises will not be fulfilled until they are completed.

  • When all promises are complete, an array of all the results is returned.

  • As long as one of them is rejected, the array is not returned, only the reason for the first rejected Promise is returned

let p1 = new Promise((resolve, reject) = > {
  resolve(42);
});
let p2 = new Promise((resolve, reject) = > {
  reject(43);
});
let p3 = new Promise((resolve, reject) = > {
  reject(44);
});

let p4 = new Promise((resolve, reject) = > {
  resolve(45);
});

// All done, return array
let p5 = Promise.all([p1, p4]);
p5.then(value= > console.log(value)); / / [45] 42,

// If there is only one error, the array is not returned, and only the reason for the first rejected Promise is returned
let p6 = Promise.all([p1, p2, p3, p4]);
p6.catch(value= > console.log(value)); / / 43
Copy the code

Promise.race()

This method also takes a single iteration object (most commonly an array) as a parameter, but responds whenever it detects that any one is resolved. So an interesting example would be to race a request interface against a setTimeout, and if setTimeout responds first, the interface’s request times out.

const p = Promise.race([
  fetch('/some-api'),
  new Promise((resolve, reject) = > {
    setTimeout((a)= > reject(new Error('Request timed out')), 3000); })); p.then(value= > {
  console.log(value);
}).catch(reason= > {
  console.log(reason);
});
Copy the code

Limitations of Promises

Promise seems like a nice solution to the problems of callback functions, but it has its limitations.

  • Once a Promise is created and a complete/reject handler is registered for it, the Promise cannot be cancelled.

  • When you are in a pending state, you have no way of knowing where the current progress is

  • Because promises can only be made once (fulfilled or rejected), the Stream pattern is more appropriate if certain events happen over and over again.

  • If the callback function is not set, errors thrown inside a Promise are not reflected outside.

Hand code

Before tearing the code can refer to the back of the Promise A+ standard translation, it is best to go to the official website to translate again, so that writing will be handy. The following code is commented on almost every sentence and links to each specification.

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class Promise {
  constructor(executor) {
    // The initial state of state is wait state
    this.state = PENDING;

    // Success value (1.3)
    this.value = undefined;

    // Cause of failure (1.5)
    this.reason = undefined;

    // Because THEN can be called many times in the same promise, all the ondepressing needs to be stored in the array (2.2.6).
    this.onResolvedCallbacks = [];

    // Since THEN can be called multiple times in the same promise, you need to store all the onRejected in the array (2.2.6)
    this.onRejectedCallbacks = [];

    const resolve = value= > {
      // This can be fulfilled only when the current is pending
      // Cannot be converted to any other state, and must have an immutable value
      if (this.state === PENDING) {
        this.state = FULFILLED;
        this.value = value;
        // Ondepressing will be implemented successively according to the original call order (2.2.6.1)
        this.onResolvedCallbacks.forEach(fn= >fn()); }};const reject = reason= > {
      // It is possible to convert to Rejected only if the value is pending
      // Cannot be converted to any other state, and must have an immutable cause
      if (this.state === PENDING) {
        this.state = REJECTED;
        this.reason = reason;
        // onRejectec callbacks are executed in the order in which they were originally invoked (2.2.6.1)
        this.onRejectedCallbacks.forEach(fn= > fn()); / / (2.2.6.2)}};Reject () if executor fails, reject()
    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }

  then(onFulfilled, onRejected) {
    // onFulfilled and onRejected are optional parameters (2.2.1)

    // If ondepressing is not a function, it must be ignored (2.2.1.1).
    onFulfilled =
      typeof onFulfilled === 'function' ? onFulfilled : value= > value;

    // If onRejected is not a function, it must be ignored (2.2.1.2)
    onRejected =
      typeof onRejected === 'function'
        ? onRejected
        : err= > {
            throw err;
          };

    // To make chained calls, specify that each then method must return a promise called promise2
    const promise2 = new Promise((resolve, reject) = > {
      Ondepressing (2.2.2) can be called after promise is fulfilled.
      if (this.state === FULFILLED) {
        // onFulfilled/onRejected must be asynchronously called, so we will simulate this with delay function (2.2.4).
        setTimeout((a)= > {
          try {
            // value as the first argument to the completion function (2.2.2.1)
            // Ondepressing function is remembered as X (2.2.7.1).
            const x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            // If ondepressing /onRejected throws an exception, promise2 must be rejected and return reject cause E (2.2.7.2).reject(e); }},0);
      }

      // Call onRejected (2.2.3) after the promise is rejected
      if (this.state === REJECTED) {
        // onFulfilled/onRejected must be asynchronously called, so we will simulate this with delay function (2.2.4).
        setTimeout((a)= > {
          try {
            // Reason as the first argument to reject function (2.2.3.1)
            // the onRejected function is called x (2.2.7.1)
            const x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            // If ondepressing /onRejected throws an exception, promise2 must be rejected and return reject cause E (2.2.7.2).reject(e); }},0);
      }

      if (this.state === PENDING) {
        this.onResolvedCallbacks.push((a)= > {
          // onFulfilled/onRejected must be asynchronously called, so we will simulate this with delay function (2.2.4).
          setTimeout((a)= > {
            try {
              const x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              // If ondepressing /onRejected throws an exception, promise2 must be rejected and return reject cause E (2.2.7.2).reject(e); }},0);
        });
        this.onRejectedCallbacks.push((a)= > {
          // onFulfilled/onRejected must be asynchronously called, so we will simulate this with delay function (2.2.4).
          setTimeout((a)= > {
            try {
              const x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              // If ondepressing /onRejected throws an exception, promise2 must be rejected and return reject cause E (2.2.7.2).reject(e); }},0); }); }});// Return promise2 (2.2.7)
    return promise2;
  }

  // Catch is a syntactic sugar for then
  catch(fn) {
    return this.then(null, fn);
  }

  finally(fn) {
    return this.then(
      value= > Promise.resolve(fn()).then((a)= > value),
      reason =>
        Promise.resolve(fn()).then((a)= > {
          throwreason; })); }}const resolvePromise = (promise2, x, resolve, reject) = > {
  // If a promise and x refer to the same object, TypeError will be used as a rejection to reject the promise (2.3.1)
  if (x === promise2) {
    return reject(new TypeError('Chaining cycle detected for promise'));
  }

  // onFulfilled and onRejected can only be called once, so a flag will be added here as a judgment (2.2.2.3 & 2.2.3.3).
  let isCalled = false;

  // If x is an object or a function (2.3.3)
  if(x ! = =null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      / / (2.3.3.1)
      const then = x.then;

      If then is a function, call it as x as this (2.3.3.2&2.3.3.3)
      if (typeof then === 'function') {
        // Receive two subsequent callbacks, the first a successful callback and the second a failed callback (2.3.3.3)
        then.call(
          x,
          y => {
            if (isCalled) return;
            isCalled = true;
            If resolvePromise is called with y, execute [[Resolve]](promise, y) (2.3.3.3.1)
            resolvePromise(promise2, y, resolve, reject);
          },
          r => {
            if (isCalled) return;
            isCalled = true;
            // If rejectPromise is invoked for r, reject the promise for r (2.3.3.3.2)reject(r); }); }else {
        // If then is not a function, execute promise with x (2.3.3.4)resolve(x); }}catch (e) {
      if (isCalled) return;
      isCalled = true;
      // If x.teng fails, e is used as a rejection for rejecting 'promise' (2.3.3.2)reject(e); }}// If then is not a function or object, then execute promise with x (2.3.4)
  else{ resolve(x); }};// Promise.resolve
Promise.resolve = function(promises) {
  if (promises instanceof Promise) {
    return promises;
  }
  return new Promise((resolve, reject) = > {
    if (promises && promises.then && typeof promises.then === 'function') {
      setTimeout((a)= > {
        promises.then(resolve, reject);
      });
    } else{ resolve(promises); }}); };// Promise.reject
Promise.reject = reason= > new Promise((resolve, reject) = > reject(reason));

// Promise.all
Promise.all = promises= > {
  return new Promise((resolve, reject) = > {
    let resolvedCounter = 0;
    let promiseNum = promises.length;
    let resolvedValues = new Array(promiseNum);
    for (let i = 0; i < promiseNum; i += 1) {(i= > {
        Promise.resolve(promises[i]).then(
          value= > {
            resolvedCounter++;
            resolvedValues[i] = value;
            if (resolvedCounter === promiseNum) {
              return resolve(resolvedValues);
            }
          },
          reason => {
            returnreject(reason); }); })(i); }}); };/ / race method
Promise.race = promises= > {
  return new Promise((resolve, reject) = > {
    if (promises.length === 0) {
      return;
    } else {
      for (let i = 0, l = promises.length; i < l; i += 1) {
        Promise.resolve(promises[i]).then(
          data= > {
            resolve(data);
            return;
          },
          err => {
            reject(err);
            return; }); }}}); };Copy the code

Yarn Global add Promises -aplus-tests: / / add Promises -aplus-tests Then use promises-aplus-tests to verify that your handwritten Promise complies with Promises A+.

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

Promises/A + specification

An open, reliable, and universal JavaScript Promise standard. Developed by developers for their reference.

A promise represents the end result of an asynchronous operation, and the primary way to interact with it is its then method, which registers two callback functions to receive either the final value of the promise or the reason for the failure.

This specification describes in detail the behavior of the THEN method. Promises/A+ can be used as A basis for implementing Promises. Therefore, the specification is stable. Promises/A+ occasionally revises the specification, but mostly to deal with specific border situations. These changes are minor and backward compatible. If we were to make a large-scale incompatible update, we would have thought carefully, discussed exhaustively, and tested rigorously in advance.

Finally, the core Promises/A+ specification does not provide how to create, resolve, and reject Promises. Instead, it focuses on providing A universal then approach. The above approach to Promises may be mentioned in other codes in the future.

1. The term

1.1. A ‘promise’ is an object or function that has then methods and behaves according to this specification.

1.2. ‘thenable’ is an object or function that defines the then method.

1.3. ‘value’ is any valid JavaScript value (including undefined, thenable, or promise)

1.4. ‘exception’ is a value thrown using a throw statement

1.5. ‘Reason’ indicates why a promise was rejected

2. Request

2.1. Promise

A promise must be one of three states: Pending, Fulfilled and Rejected.

  • 2.1.1. When the current state is pending, a promise:

    • 2.1.1.1 This can be changed into a pity or rejected state
  • This is a big pity. 2.1.2. This is a big promise.

    • 2.1.2.1 Cannot be converted to any other state

    • 2.1.2.2 Must have an immutable value

  • 2.1.3. When the current state is Rejected, a promise is rejected.

    • 2.1.3.1 Cannot be converted to any other state

    • 2.1.3.2 There must be an immutable cause

Immutability here refers to identity (equality can be determined by ===), not to a deeper immutability. (If value or Reason is a reference type, only the reference addresses are required to be equal, but the attribute values can be modified)

2.2. thenmethods

A promise must provide a THEN method to access its current or final value or the reason it was rejected.

A Promise’s then method takes two arguments:

promise.then(onFulfilled, onRejected);
Copy the code
  • 2.2.1 onFulfilled and onRejected are both optional parameters.

    • 2.2.1.1 If ondepressing is not a function, it will be ignored

    • 2.2.1.2 If onRejected is not a function, it is ignored

  • 2.2.2 If ondepressing is a function:

    • 2.2.2.1 It must be called after the promise completion and take the promise value as its first argument.

    • 2.2.2.2 Cannot be invoked before promise is completed

    • 2.2.2.3 This function can only be called once

  • 2.2.3 If onRejected is a function:

    • 2.2.3.1 It must be called after a promise has been rejected and take the reason for the promise as its first argument.

    • 2.2.3.2 It cannot be invoked before promise is rejected

    • 2.2.3.3 This function can be called only once

  • 2.2.4 onFulfilled and onRejected can only be called if the execution context stack contains only platform code. [1]

  • 2.2.5 onFulfilled and onRejected must be called as a function (i.e. without this). [2]

  • 2.2.6 THEN can be called multiple times on the same promise

    • 2.2.6.1 When the Promise is completed, all corresponding ondepressing callbacks must be executed in the order of their original invocation.

    • 2.2.6.2 When a Promise is a rejected state, all corresponding onRejected callbacks must be executed in the order in which they were originally called.

  • 2.2.7 Each THEN method must return a Promise [3].

    promise2 = promise1.then(onFulfilled, onRejected);
    Copy the code
    • 2.2.7.1 If ondepressing or onRejected returns a value x, run the following Promise process: [[Resolve]](promise2, x)

    • 2.2.7.2 If ondepressing or onRejected throws an exception E, promise2 must refuse to perform and return rejection cause E

    • 2.2.7.3 If ondepressing is not a function and promise1 performs successfully, promise2 must perform successfully and return the same value

    • 2.2.7.4 If onRejected is not a function and promise1 rejects it, promise2 must reject it and return the same reject

2.3. Promise resolution process

The Promise resolution procedure is an abstract operation that accepts a Promise and a value that we can express as [[Resolve]](Promise, x). If X is a Thenable object, the resolvers will attempt to accept the state of X, Otherwise, the promise is implemented with the value of x.

This treatment of Thenales makes the promise implementation more universal, as long as it exposes A THEN approach that is compatible with Promises/A+ norms. It also allows implementations that follow the Promise/A+ specification to coexist well with less formal but usable implementations.

To run [[Resolve]](promise, x), perform the following steps:

  • 2.3.1 If a Promise and X refer to the same object, TypeError will be used as a rejection to reject the promise.

  • 2.3.2 If X is a promise, then the promise will accept its state [4] :

    • 2.3.2.1 If X is in wait state, the promise must remain in wait state until x is completed or rejected.

    • 2.3.2.2 If x is complete, implement the promise with the same value

    • 2.3.2.3 If X is rejected, reject the promise for the same reason

  • 2.3.3 If x is an object or a function,

    • 2.3.3.1 Assign x. teng to THEN. [5]

    • 2.3.3.2 If an error e is thrown when taking the value of x. teng, e is used as the rejection reason to reject the promise

    • 2.3.3.3 If THEN is a function, it is called with x as the function’s scope this. Pass two callback functions as arguments, the first called resolvePromise and the second called rejectPromise:

      • 2.3.3.3.1 If resolvePromise is called with y, execute [[Resolve]](promise, y)

      • 2.3.3.3.2 If a rejectPromise is invoked for r, reject the promise as r

      • 2.3.3.3.3 If both resolvePromise and rejectPromise are called, or if the same parameter is called more than once, the first call is preferred and the remaining calls are ignored.

      • 2.3.3.3.4 If calling THEN throws an exception e

        • 2.3.3.3.4.1 If both resolvePromise and rejectPromise are invoked, ignore it

        • 2.3.3.3.4.2 Otherwise, e will be used as rejection for rejecting this promise

    • 2.3.3.4 If then is not a function, the promise is executed with an x argument

  • 2.3.4 If then is not a function or object, then a promise is executed with x as an argument

If a promise is resolved by an object in a loop’s Thenable chain, and the recursive nature of [[Resolve]](promise, thenable) causes it to be called again, the algorithm above will lead to infinite recursion. The algorithm does not require it, but encourages the agent to detect the presence of such recursions, and if so, reject the promise with an identifiable TypeError as a rejection [6].

3. Comments


  1. The “platform code” here means the engine, environment and promise implementation code. In practice, ensure that the onFulfilled and onRejected are executed asynchronously and should be executed in a new execution stack after the event cycle in which the THEN method is called. The event queue can be achieved using a “macro-task” mechanism, similar to setTimeOut or setImmediate, or using a “micro-task” mechanism, Similar to MutationObserver or process.nexttick. Because the Promise implementation is considered platform code, it may itself contain a task scheduling queue or springboard in which handlers are invoked. ↩ ︎

  2. In strict mode this is undefined, while in non-strict mode this is a global object. ↩ ︎

  3. The code implementation can allow promisE2 === PROMISe1 if all requirements are met. Each implementation should document whether and under what conditions it allows PROMISE2 === PROMISe1. ↩ ︎

  4. In general, x is considered a true promise if it matches the current implementation. This rule allows those exceptions to accept Promises that meet known requirements. ↩ ︎

  5. In this step, we first store a reference to x.teng, and then test and call the reference to avoid accessing the x.teng property multiple times. This precaution ensures that the property is consistent because its value can be changed during retrieval calls. ↩ ︎

  6. The implementation should not impose a limit on the depth of the Thenable chain and assume that recursion beyond this limit is an infinite loop. Only true loop recursion should result in TypeError exceptions; If the Thenable is different across an infinite chain, recursion is always the correct behavior. ↩ ︎