The quiz about promise



PromiseA + specification

let p = new Promise((resolve,reject) = >{
    try{... resolve(val) }catch(err) {
        reject(err)
    }
    
})
p.then((val) = >{... })Copy the code

In basic usage, the basic structure of promises should look like this

class Promise{
  / / the constructor
  constructor(executor){
    let resolve = (a)= >{};let reject = (a)= >{}; executor(resolve, reject); } then(onFulfilled,onRejected){} }Copy the code

Explain:

  • Promise accepts a function as an argument, which in the specification is called executor
  • The executor function takes two arguments, resolve and reject, which are actually defined inside the Promise function. See below for details

The specification largely represents the design principles of Promise, and we refined it step by step according to the A+ specification. You may have questions about some of these implementations along the way, but don’t worry, wait until the full implementation is complete and you’ll see

The state of the Promise

  • This is a big pity, which can be changed into a pity (failure) and rejected (failure).
  • This is a big pity: The successful state cannot be changed into other states, and there must be an unchangeable value.
  • The Rejected state cannot be converted into other states and there must be an unchangeable reason.


promise1.0

class MyPromise{
  constructor(executor){
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    let resolve = value= > {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value; }};let reject = reason= > {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason; }};// If the executor executes incorrectly, execute reject
    try{
      executor(resolve, reject);
    } catch(err) { reject(err); }}}Copy the code

Then method

  • A promise must provide onethenMethod to access its current value, final value, and cause. The promisethenThe method takes two arguments:
promise.then(onFulfilled, onRejected)
Copy the code
  • thenThe method can be the samepromiseCall several times

This is a big pity. When the state is fulfilled, onFulfilled. When the state is fulfilled, onFulfilled.

class MyPromise {
  constructor(executor){... } then(onFulfilled,onRejected) {if (this.state === 'fulfilled') {
      onFulfilled(this.value);
    };
    if (this.state === 'rejected') {
      onRejected(this.reason); }; }}Copy the code

Implementing asynchronous calls

This has implemented a basic prototype of a promise, but it does not yet allow for asynchronous calls, such as:

new MyPromise((resolve,reject) = >{
	setTimeout((a)= >{
    console.log(123)
    resolve()
  },100)
}).then((a)= >{
  console.log(456)})Copy the code

You’ll notice that the output is also not as expected, because resolve is executed asynchronously, so resolve occurs after THEN, and state remains pendding. So we made the following transformation

class Promise{
  constructor(executor){
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
    let resolve = value= > {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onResolvedCallbacks.forEach(fn= >fn());   // }};let reject = reason= > {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn= >fn()); }};try{
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
  then(onFulfilled,onRejected) {
    if (this.state === 'fulfilled') {
      onFulfilled(this.value);
    };
    if (this.state === 'rejected') {
      onRejected(this.reason);
    };
    // Store both functions when state is pending
    if (this.state === 'pending') {
      this.onResolvedCallbacks.push((a)= >{
        onFulfilled(this.value);
      })
      this.onRejectedCallbacks.push((a)= >{
        onRejected(this.reason); })}}}Copy the code

Following the idea of publish and subscribe, use an array to store the two functions in then, which will be executed when an asynchronous task starts. Why use arrays? Since a promise can call more than one THEN, we push the parameters into an array each time we call then, and foreach executes them in resolve or reject, for example:

// Multiple THEN cases
let p = new Promise(a); p.then(); p.then();Copy the code

In addition, we must ensure that the two function parameters onFulfilled and onRejected in THEN are executed last, so we should put them in the asynchronous queue. The implementation method is to wrap them with setTimeout

. if (this.state === 'pending') {
  this.onResolvedCallbacks.push((a)= >{
    setTimeout((a)= >{
      onFulfilled(this.value);
    },0)})this.onRejectedCallbacks.push((a)= >{
    setTimeout((a)= >{
      onRejected(this.reason);
    },0)})}Copy the code

Chain calls

Promise’s greatest use for chain-calling is to solve the problem of callback hell. The chain invocation implementation is then implemented by returning a promise in the then function, i.e

promise2 = promise1.then(onFulfilled, onRejected);
Copy the code

According to A+ specification:

  • ifonFulfilledoronRejectedReturn a valuex, run the followingPromise resolution process:[[Resolve]](promise2, x)
  • ifonFulfilledoronRejectedThrow an exceptione,promise2Execution must be rejected and a rejection must be returnede
  • ifonFulfilledIs not a function andpromise1Successful execution,promise2Must execute successfully and return the same value
  • ifonRejectedIs not a function andpromise1Refuse to enforce,promise2Execution must be rejected and the same data returned

By specification, we should compare x and promise, so we should implement a resolvePromise(promise,x,resolve,reject), so the then method implementation should look like this:

...let promise2 = new Promise((resolve, reject) = >{
      if (this.state === 'fulfilled') {
        let x = onFulfilled(this.value);
        // the resolvePromise function handles the relationship between its return promise and the default promise2
        resolvePromise(promise2, x, resolve, reject);
      };
      if (this.state === 'rejected') {
        let x = onRejected(this.reason);
        resolvePromise(promise2, x, resolve, reject);
      };
      if (this.state === 'pending') {
        this.onResolvedCallbacks.push((a)= >{
          let x = onFulfilled(this.value);
          resolvePromise(promise2, x, resolve, reject);
        })
        this.onRejectedCallbacks.push((a)= >{
          let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); }}}));// Return promise, complete the chain
    returnpromise2; }}Copy the code

The following is mainly the implementation process of resolvePromise: there are three cases stipulated in the specification: X is equal to promise; X as the promise; X is an object or a function. Promise resolution is an abstract operation that takes a Promise and a value, which we represent as [[Resolve]](Promise, x). To run [[Resolve]](Promise, x), follow these steps: Refer to the Promise settlement process. X is equal to a Promise. If a promise and x refer to the same object, reject the promise as TypeError.

  1. X to Promise
  • If x is a Promise, make the Promise accept the state of X.
  • If X is in wait state, the promise needs to remain in wait state until x is executed or rejected.
  • If x is in the execution state, execute the promise with the same value.
  • If X is in the reject state, reject the promise with the same grounds.
  1. X is an object or function

If x is an object or function:

  • Assign x. teng to then.
  • If an error e is thrown when taking the value x. teng, reject the promise based on e.
  • If then is a function, x is called as the function’s scope this. Pass two callback functions as arguments, the first called resolvePromise and the second called rejectPromise:
    • If resolvePromise is called with the value y, run [[Resolve]](promise, y)
    • If rejectPromise is invoked with argument r, reject the promise with argument r
    • If both resolvePromise and rejectPromise are invoked, or if the same parameter is invoked more than once, the first call is preferred and the remaining calls are ignored
    • If calling the then method raises exception e:
      • If a resolvePromise or rejectPromise has already been invoked, it is ignored
      • Otherwise, reject the promise based on e
    • If then is not a function, execute the promise with an x argument
  • If x is not an object or function, execute the promise 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 recursion, and if so, reject the promise with an identifiable TypeError as a justification.

function resolvePromise(promise2, x, resolve, reject) {
  if (x === promise2) {
    reject(new TypeError('Circular reference'));
  }
  if (x instanceof Promise) {
    if (x.state === PENDING) {
      x.then(
        y= >{ resolvePromise(promise2, y, resolve, reject); }, reason => { reject(reason); }); }else{ x.then(resolve, reject); }}else if (x && (typeof x === 'function' || typeof x === 'object')) {
    // Avoid multiple calls
    let called = false;
    try {
      // assign x. teng to then
      let then = x.then;
      if (typeof then === 'function') {
        // If then is a function, x is called in the function's scope this.
        // Pass two callback functions as arguments, the first called resolvePromise and the second called rejectPromise
        // 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
        then.call(
          x,
          // If resolvePromise is called with the value y, run [[Resolve]](promise, y)
          y => {
            if (called) return;
            called = true;
            resolvePromise(promise2, y, resolve, reject);
          },
          // If rejectPromise is invoked with argument r, reject the promise with argument r
          r => {
            if (called) return;
            called = true; reject(r); }); }else {
        // If then is not a function, execute a promise with an x argumentresolve(x); }}catch (e) {
      // If an error e is thrown when taking the value x. teng, reject a promise based on e
      // If calling the then method throws an exception e:
      // If resolvePromise or rejectPromise has already been invoked, ignore it
      // Otherwise use e as the basis for rejecting the promise
      if (called) return;
      called = true; reject(e); }}else {
    // If x is not an object or function, execute a promise with x as an argumentresolve(x); }}Copy the code

Finally, we’ll complete the rest of the Promise API:

Promise.reject = function(val){
  return new Promise((resolve,reject) = >{
    reject(val)
  });
}
/ / race method
Promise.race = function(promises){
  return new Promise((resolve,reject) = >{
    for(let i=0; i<promises.length; i++){ promises[i].then(resolve,reject) }; })}//all (get all promises, execute then, place the results in an array, and return them together)
Promise.all = function(promises){
  let arr = [];
  let i = 0;
  function processData(index,data,resolve){
    arr[index] = data;
    i++;
    if(i == promises.length){
      resolve(arr);
    };
  };
  return new Promise((resolve,reject) = >{
    for(let j=0; j<promises.length; j++){ promises[i].then(data= >{
        processData(j,data,resolve);
      },reject);
    };
  });
}
Copy the code

Reference: juejin. Cn/post / 684490… Juejin. Cn/post / 684490… Juejin. Cn/post / 684490…