JS deep excavation: hand tear Promise detailed code complete introduction

catalog:
1,new Promise()
2,.then()And chain call
3,.resolve()and.reject()
4,.all()and.race()

Before tearing Promise by hand, we have to understand the characteristics of Promise.
First of all, Promise is essentially a constructor, which can create Promise object instances to meet the requirements of asynchronous programming.
The Promise features we need to implement in this article are as follows:

1,new Promise((resolve,reject) => {})
As a constructor, the first thing we need to implement is its basic architecture. We first implement the most basic functions:

1)status Storage status value and error Storage successful/Value of failure status
2)resolve and reject Method change state
3)then Method to implement the callback after the state changes

class MyPromise {
  constructor(executor) {
    this.status = 'pending';

    this.value;   // Value of success status
    this.error;   // Value of failure status

    this.resolveCallbacks = [];
    this.rejectedCallbacks = [];

    let resolve = (res) => {
    }

    let reject = (err) => {
    }

    executor(resolve, reject)
  }

  then(onFulfilled, onRejected) {
  }

  catch() {

  }
}

Supplement the contents of resolve, reject, then and catch

1)then The most basic function is calling the callback function after the state changes.
2)catch The essence is that there is only failure.then
class MyPromise {
  constructor(executor) {
    this.status = 'pending';

    this.value;   // Value of success status
    this.error;   // Value of failure status

    this.resolveCallbacks = [];
    this.rejectedCallbacks = [];

    let resolve = (res) => {
      // Change state and execute callback
      if (this.status === 'pending') {
        this.status = 'resolved';
        this.value = res;
      }
    }

    let reject = (err) => {
      // Change state and execute callback
      if (this.status === 'pending') {
        this.status = 'rejected';
        this.error = err;
      }
    }

    executor(resolve, reject)
  }

  then(onFulfilled, onRejected) {
    // The essence is to execute the corresponding callback function if the current state is resolved or rejected
    if (this.status === 'resolved') {
      onFulfilled(this.value);
    }

    if (this.status === 'rejected') {
      onRejected(this.error)
    }
  }

  catch(rejected) {
    return this.then(null, rejected)
  }
}

In this way, we can call the constructor to realize the most basic functions

let pro1 = new MyPromise((resolve, reject) => {
  resolve('succeed');
}).then(res => {
  console.log(res);
})

console.log(pro1);  // succeed

2,. then() and chain call
There are several problems with the writing of then above:
1) When the state change is in an asynchronous function, it is executed at this time then cannot execute the callback immediately. It should be executed together with the change of waiting state
2). The callback in then should be placed in the asynchronous function, so you need to add setTimeout
3). The return value of then must be Promise object, so the result of each asynchronous processing is set to x, and the returned state and value of Promise 2 need to be determined according to X

The resolvePromise method is introduced here to determine the status and value of the returned promise 2.
The initial improvements are as follows:

MyPromise.then(onFulfilled, onRejected) {
    // The essence is to execute the corresponding callback function if the current state is resolved or rejected
    let promise2;

    promise2 = new MyPromise((resolve, reject) => {
      if (this.status === 'resolved') {
        setTimeout(() => {
          let x = onFulfilled(this.value);
          resolvePromise(promise2, x, resolve, reject);
        })
      }

      if (this.status === 'rejected') {
        setTimeout(() => {
          let x = onRejected(this.error);
          resolvePromise(promise2, x, resolve, reject);
        })
      }

      if (this.status === 'pending') {
        this.resolveCallbacks.push(() => {
          setTimeout(() => {
            let x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          })
        });
        this.rejectedCallbacks.push(() => {
          setTimeout(() => {
            let x = onRejected(this.error);
            resolvePromise(promise2, x, resolve, reject);
          })
        });
      }
    })

    return promise2;
  }

The implementation of the function resolvePromise is the core of the Promise implementation. It contains several important rules, which are briefly summarized as follows:
1) If the return value x is a numerical value, Promise in the status of fulfilled is returned, and the value is X
2) If the return value x is a Promise object, continue the recursive call
3) Only when the return value x is a Promise object in the rejected state, the Promise object in the failed state is returned

function resolvePromise(promise2, x, resolve, reject) {
  // Circular reference error
  if (x === promise2) {
    // reject error thrown
    return reject(new TypeError('Chaining cycle detected for promise'));
  }
  // Lock to prevent multiple calls
  let called;

  // x is not null and x is an object or function
  if (x != null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      // A + specifies the then method of declaring then = x
      let then = x.then;
      // If then is a function, it defaults to promise
      if (typeof then === 'function') {
        // Let then execute. The first parameter is this, followed by successful callbacks and failed callbacks
        then.call(x, y => {
          // Success and failure can only call one
          if (called) return;
          called = true;
          // If the result of resolve is still promise, continue to execute recursively
          resolvePromise(promise2, y, resolve, reject);
        }, err => {
          // Success and failure can only call one
          if (called) return;
          called = true;
          reject(err);// If you fail, you fail
        })
      } else {
        resolve(x); // Just succeed directly
      }
    } catch (e) {
      // It also belongs to failure
      if (called) return;
      called = true;
      // If there is an error in getting then, don't continue
      reject(e);
    }
  } else {
    resolve(x);
  }
}

At the end of the then implementation, some details need to be added. For example, if the incoming onFullfilled is not a function, it will ignore onFullfilled and directly return value; There are also previous calls to add resolve and reject to the callback array

onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : error => error;

3,. resolve() and reject()
Promise can also directly call the resolve and reject methods. Its essence is new Promise and then change the state.

MyPromise.resolve = (value) => {
  return new MyPromise((resolve, reject) => {
    resolve(value);
  })
}

MyPromise.reject = (value) => {
  return new MyPromise((resolve, reject) => {
    reject(value);
  })
}

4,. all() and race()
First, let's talk about the all() method. Its essence is that it returns a value only when all Promise objects in the parameter are fully completed. It has the following characteristics:
Promise.all([promise1,promise2,...])
1) resolve only when all promise objects in the array are resolved, and the promise object is returned. Value is the successful value of all promise objects
2) If there is a promise object reject, the promise result of the first reject is returned
3) The promises of the parameter only needs to be an iterative object. If the Promise object is not passed, the original value can be returned directly.

MyPromise.all = (promises) => {
  // promises is an array
  if (!Array.isArray(promises)) {
    throw ('Must be an array')
  }
  let res = []

  return new MyPromise((resolve, reject) => {
    for (let i = 0; i < promises.length; i++) {
      if (promises[i].then) {
        // Is a promise object then
        promises[i].then(r => {
          res.push(r);
          if (res.length === promises.length) {
            // All success
            return resolve(res);
          }
        }, error => {
          return reject(error);
        })
      } else {
        // If it is not a promise object, value is stored directly
        res.push(promises[i]);
        if (res.length === promises.length) {
          // All success
          return resolve(res);
        }
      }
    }
  })
}

The race() method is opposite to all. It only returns parameters. All Promise objects change their state the earliest. Its characteristics are as follows:
Promise.race(promises)
1) When the state of a Promise in the array changes to fully / rejected, race returns the Promise state and value
2) Inject the resolve of race directly into the callback of each object in the array

MyPromise.race = (promises) => {
  return new MyPromise((resolve, reject) => {
    for (let i = 0; i < promises.length; i++) {
      promises[i].then(resolve, reject);
    }
  })
}

All Promise's hand tears are finished.

Keywords: Javascript node.js Front-end ECMAScript React

Added by divadiva on Fri, 28 Jan 2022 01:09:59 +0200