introduce

Asynchronous behavior is the foundation of JavaScript, but has not been implemented well in the past. In earlier JavaScript, only callback functions were supported to indicate that an asynchronous function operation was complete. Concatenating multiple asynchronous operations is a common problem that often requires deeply nested callback functions (colloquially known as “callback hell”) to solve.

To address the problem of hell callbacks, ES6 implements A Promise that expresses asynchronous behavior as A flow of synchronous behavior, according to the Promise/A+ specification.

Promise/A + specification

A Promise represents the end result of an asynchronous operation, primarily by calling the Promise’s then method, which accepts a callback function that accepts either the result of the Promise’s success or the reason for its failure.

1, terminology,

  1. A promise is an object or function that has then methods that conform to this standard
  2. Thenable is an object or function defined by the then method
  3. Value is a JavaScript valid value (undefined, thenable, promise)
  4. Exception is an error thrown by a throw statement
  5. Reason is a value that indicates why a promise failed

2, requirements

  1. State of Promise

    A promise has only one state (pending, fullfilled, rejected)

    1. Pending state:
      • It may change to the fullfilled or Rejected state
    2. In the fullfilled state:
      • No state can be changed to another state
      • There must be a value and it cannot be changed
    3. In the Rejected state:
      • No state can be changed to another state
      • There must be a reason and it cannot be changed

    Note: Immutable means identical, but does not mean immutable at a deeper level (when value or reason is not fundamental, only reference addresses are required to be equal, but attribute values can be changed).

  2. Then method

    A promise must provide a THEN method that gets the current or final value or Reason

    A Promise’s then method takes two arguments:

    promise.then(onFullfilled, onRejected)

    1. OnFullfilled and onRejected are both optional parameters:

      • If onFullfilled is not a function it will be ignored
      • If onRejected is not a function it will be ignored
    2. OnFullfilled features:

      • It must be called after the promise is fulfilled and accepts a parameter value
      • inpromiseIt cannot be called until execution ends
      • It is called at most once
    3. OnRejected features:

      • It must be called after the Promise state rejected and takes a parameter, Reason
      • inpromiseIt cannot be called until execution is rejected
      • It is called at most once
    4. OnFulfilled or onRejected is called only after the execution environment stack contains only platform code

    5. Ondepressing and onRejected will be called as a function (i.e., default this refers to global, strictly undefined).

    6. Promise’s then can be called multiple times in chain

      • When the promise state is a big pity, all the onFulfilled callbacks will be executed in the order in which they were registered
      • When the Promise state is Rejected, all onRejected callbacks are executed in the order in which they were registered
    7. The then method must return a Promise object

      promise2 = promise1.then(onFulfilled, onRejected);

      1. OnFulfilled or onRejected returns an X, which will be fulfilled by onFulfilled or onRejected[[Resolve]](promise2, x)Deal with parsing
      2. If ondepressing or onRejected throws an exception, then promise2 must catch the error (accept a Reason parameter).
      3. If ondepressing is not a function and the promise1 state is a depressing, then promise2 must receive the same value as promse1
      4. If onRejected is not a function and the status of promise1 is Rejected, promise2 must accept the same value as Promise1 (Reason)
  3. Promise handler

    Promise resolution is an abstract operation that takes a Promise and a value, which we represent as [[Resolve]](Promise, x). If x is of type Thenable, it attempts to generate a Promise to handle x, Otherwise it will simply resolve x

    This thenable feature makes Promise implementation more universal: as long as it exposes A THEN method that follows the Promise/A+ protocol; This also allows implementations that follow the Promise/A+ specification to coexist well with less formal but usable implementations.

    (Chain call, layer by layer). It also allows “assimilation” of THEN methods that don’t fit Promises/A+

  4. The current state of a Promise can only be one of Pending, Fufilled, and Rejected. The switching state can only be converted from pending to the other two states irreversibly

  5. The then method in a Promise can accept two parameters as a callback when the Promise state changes, and then returns a new Promise that can be called multiple times by the same Promise

implementation

skeleton

Build the skeleton of the architecture based on how users use it

Class Promise{constructor(execute) {this.status = states.pending this.value = null this.reason = null this.fullfilledCbs = []; This. rejectedCbs= []; // Store failed callback this.rejectedCbs= []; const resolve = () => { } const reject = () => { } execute(resolve,reject) } then(fullfilledCbs, rejectedCbs) { return new Promise() } }Copy the code

Simple implementation

We don’t think about all the cases, we just implement it, we don’t think about asynchrony, return value and so on.

const STATES = { PENDING: "PENDING", FULFILLED: "FULFILLED", REJECTED: "REJECTED", }; class Promise { constructor(execute) { this.status = STATES.PENDING; this.value = null; this.reason = null; this.fullfilledCbs = []; this.rejectedCbs= []; const resolve = (value) => { if (this.status === STATES.PENDING) { this.value = value; this.status = STATES.FULFILLED; this.fullfilledCbs.forEach((_i) => _i()); }}; const reject = (err) => { if (this.status === STATES.PENDING) { this.reason = err; this.status = STATES.REJECTED; this.rejectedCbs.forEach((_i) => _i()); }}; try { execute(resolve, reject); } catch (e) { reject(e); } } then(onFulfilled, onRejected) { if (this.status === STATES.FULFILLED) { onFulfilled(this.value); } else if (this.status === STATES.REJECTED) { onRejected(this.reason); } this. Status === states.pending {// If (this. Status === states.pending) {// If (this. This.fullfilledcbs.push (() => {ondepressing (this.value)}); this.rejectedCbs.push(()=> { onRejected(this.reason); })}}}Copy the code

Test the

Const promise = new promise ((resolve, reject) => {setTimeout(() => {resolve(' succeed ')); }, 1000); }). Then ((data) => {console.log('success', data)}, (err) => {console.log('faild', err)}) Wait 1 second and the console displays successCopy the code

Then method completion

const STATES = { PENDING: "PENDING", FULFILLED: "FULFILLED", REJECTED: "REJECTED", }; Const resolvePromise = (promise2, x, resolve, reject) If (promise2 === x) {return reject(new TypeError(" X and promise2 cannot be the same person ")); } // if ((typeof x === "object" &&x! = null) || typeof x === "function") { let called; Then let then = x.hen; // Object.defineProperType if (typeof then === "function") {// Use thene.call (). This is a big pity (y) => {// forget // forget (y)// this is a big pity; } called = true; ResolvePromise (promise2, y, resolve, reject); resolvePromise(promise2, y, resolve, reject); }, (r) => { // onRejected if (called) { return; } called = true; reject(r); // Take the failed result of a promise and pass it down}); } else { if (called) { return; } called = true; resolve(x); // x is not a function, it is an object}} Catch (err) {if (called) {return; } called = true; reject(err); } } else { resolve(x); }}; class Promise { constructor(execute) { this.status = STATES.PENDING; this.value = null; this.reason = null; FullfilledCbs = []; This. rejectedCbs = []; // Store failed callback this.rejectedCbs = []; const resolve = (value) => { if (this.status === STATES.PENDING) { this.value = value; this.status = STATES.FULFILLED; this.fullfilledCbs.forEach((_i) => _i()); }}; const reject = (err) => { if (this.status === STATES.PENDING) { this.reason = err; this.status = STATES.REJECTED; this.rejectedCbs.forEach((_i) => _i()); }}; try { execute(resolve, reject); } catch (e) { reject(e); } // then(onFulfilled, onFulfilled) {// Then (onFulfilled, onFulfilled) === "function"? onFulfilled : (val) => val; onRejected = typeof onRejected === "function" ? onRejected : (err) => { throw err; }; let promise2 = new Promise((resolve, (this. Status === STATES. Depressing) {// Sync cannot use promise2, SetTimeout (() => {try {let x = ondepressing (this. Value); ResolvePromise (promise2, x, resolve, reject); resolvePromise(promise2, x, resolve, reject); } catch (err) { reject(err); }}, 0); } if (this.status === STATES.REJECTED) { setTimeout(() => { try { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (err) { reject(err); }}, 0); } this. Status === states.pending {// If (this. Status === states.pending) {// If (this. This.fullfilledcbs.push (() => {setTimeout(() => {try {let x = ondepressing (this.value); resolvePromise(promise2, x, resolve, reject); } catch (err) { reject(err); }}, 0); }); this.rejectedCbs.push(() => { setTimeout(() => { try { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (err) { reject(err); }}, 0); }); }}); return promise2; }}Copy the code

The specific reasons for each step are written in the notes, which will not be described here. Test it out:

Const promise = new promise ((resolve, reject) => {setTimeout(() => {resolve(" succeed ")); }, 1000); }) .then( (data) => { console.log("success", data); }, (err) => { console.log("faild", err); } ) .then( () => { console.log("success2"); Return new Promise((resolve, reject) => {setTimeout(() => {resolve(" succeed ")); }, 0); }); }, () => { console.log("faild2"); } ) .then( (data) => { console.log("success3", data); }, (err) => { console.log("faild2", err); }); 1 second later: Success Success2 Success3 Success 3Copy the code

Success!