30 minutes, take you to realize a promise that meets the specification

Time:2020-10-1

preface

aboutPromiseThere are many excellent articles on principle analysis in nuggets. But the author is always in theIf you read it, you can write itThis is the purpose of this article, in order to sort it outPromiseWrite a wave of code from scratch, but also facilitate their future review.

 

The role of promise

PromiseyesJavaScriptAsynchronous programming is a popular solution, it appears to solveBack to hellUsers can write asynchronous code in a chained way. The author will not introduce the specific usage. You can refer to Ruan Yifeng’s ES6 promise tutorial.

 

Pre class knowledge

Observer model

What is the observer model

Observer pattern defines a one to many dependency, which allows multiple observer objects to listen to a target object at the same time. When the state of the target object changes, all the observer objects will be informed so that they can be updated automatically.

PromiseIs based onObserver’s design patternRealized,thenThe function to be executed is crammed into the observer array whenPromiseWhen the state changes, all functions in the observation group array are executed.

Event cycle mechanism

realizationPromiseInvolvingJavaScriptEvent loop mechanism inEventLoop, and the concept of macro task and micro task.

The flow chart of event loop mechanism is as follows:

30 minutes, take you to realize a promise that meets the specification

You can take a look at this Code:

console.log(1);

setTimeout(() => {
  console.log(2);
},0);

let a = new Promise((resolve) => {
  console.log(3);
  resolve();
}).then(() => {
  console.log(4);
}).then(() => {
  console.log(5);
});

console.log(6);

If you can’t say the output results at once, we suggest that you can check it firstevent loopThere are many excellent articles in the Nuggets.

Promise / A + specification

Promises / A + is a community norm, if you want to write a canonicalPromiseWe need to follow this standard. After that, we will improve our own writing according to the specificationsPromise

 

Promise core knowledge points

I’m writingPromiseLet’s go through a few important points.

executor

//Create promise object x1
//And execute the business logic in the executor function
function executor(resolve, reject){
  //Business logic processing success result
  const value = ...;
  resolve(value);
  //Failure results
  // const reason = ...;
  // reject(reason);
}

let x1 = new Promise(executor);

firstPromiseIs a class that receives an execution functionexecutorIt takes two parameters:resolveandrejectThe two parameters arePromiseTwo internally defined functions are used to change the state and execute the corresponding callback function.

becausePromiseIt does not know whether the execution result fails or succeeds. It just provides a container for asynchronous operations. The actual control is in the hands of the user. The user can call the above two parameters to tellPromiseWhether the result is successful or not, and the business logic processes the result(value/reason)As a parameter toresolveandrejectTwo functions that execute the callback.

Three states

PromiseThere are three states:

  • pending: waiting
  • resolved: succeeded
  • rejected: failed

stayPromiseThere are only two possibilities for a change in the state ofpendingBecomeresolvedOr frompendingBecomerejected, as shown in the following picture (from promise Mini Book)

30 minutes, take you to realize a promise that meets the specification

And it should be noted that once the state changes, the state will not change again, and this result will always follow. That is to say, when we are inexecutorCalled in functionresolveAfter that, it is called.rejectThere is no effect, and vice versa.

//And execute the business logic in the executor function
function executor(resolve, reject){
  resolve(100);
  // after calling resolve, reject is invalid.
  //Because the state has changed to resolved and will not change again
  reject(100);
}

let x1 = new Promise(executor);

then

every lastpromiseIt’s all onethenMethod, this is whenpromiseAfter the result is returned, the callback function to be executed has two optional parameters:

  • onFulfilled: successful callback;
  • onRejected: failed callback;

The picture below (quoted from promise Mini Book)

30 minutes, take you to realize a promise that meets the specification

// ...
let x1 = new Promise(executor);

//X1 delay bound callback function onresolve
function onResolved(value){
  console.log(value);
}

//X1 delay bound callback function onrejected
function onRejected(reason){
  console.log(reason);
}

x1.then(onResolved, onRejected);

 

General process of handwritten promise

Here, let’s go through a simple handwritten onePromiseThe general process is as follows:

Executor and three states

  • new PromiseYou need to pass aexecutorExecutor function, in the constructor,The actuator function is executed immediately
  • executorThe execution function takes two parameters, which areresolveandreject
  • PromiseOnly frompendingreachrejectedOr frompendingreachfulfilled
  • PromiseOnce confirmed, the state solidifies and does not change

Then method

  • be-allPromiseAll of themthenmethod,thenReceive two parameters, which arePromiseSuccessful callbackonFulfilled, and failed callbacksonRejected
  • If thethenWhen,PromiseIf it has been successful, it will be executedonFulfilled, and willPromiseIf thePromiseHas failed, then executeonRejected, and willPromiseThe reason for the failure is passed in as an argument; ifPromiseThe state ofpending, you need toonFulfilledandonRejectedThe function is stored and the corresponding function is executed in turn after the state is determined (observer mode)
  • thenParameters ofonFulfilledandonRejectedIt can’t be passed on,Promise Value penetration is possible

Chain call and process then return value

  • PromisesurethenMany times,PromiseOfthenMethod returns a newPromise
  • IfthenIf a normal value is returned, this result will be returned(value)As an argument, pass to the nextthenSuccessful callback of(onFulfilled
  • IfthenIf an exception is thrown in the(reason)As an argument, pass to the nextthenFailed callback for(onRejected)
  • IfthenIt’s apromiseOr something elsethenableObject, then we need to wait for thispromiseThe implementation is complete,promiseIf you succeed, go to the next onethenIf it fails, go to the next onethenFailed callback for.

The above is the general implementation process. If you are confused, it doesn’t matter. As long as you have an impression, we will talk about it one by one.

Then we will start to implement a simple example to explain.

30 minutes, take you to realize a promise that meets the specification

 

First edition (starting with a simple example)

Let’s first write a simple version. This version does not support state and chain calls, and only supports one callthenmethod.

Let’s have one

let p1 = new MyPromise((resolve, reject) => {
    setTimeout(() => {
      Resolved ('succeeded ');
    }, 1000);
})

p1.then((data) => {
    console.log(data);
}, (err) => {
    console.log(err);
})

The example is very simple1sThen returnsucceed, and in thethenMedium output.

realization

We define aMyPromiseClass, and then we write code in it. The specific code is as follows:

class MyPromise {
  //TS interface definition
  constructor (executor: executor) {
    //The value used to hold resolve
    this.value = null;
    //The value used to save reject
    this.reason = null;
    //Successful callback to save then
    this.onFulfilled = null;
    //Failed callback to save then
    this.onRejected = null;

    //Resolve parameter of executor
    //Used to change the state and execute a successful callback in then
    let resolve = value => {
      this.value = value;
      this.onFulfilled && this.onFulfilled(this.value);
    }

    //Reject parameter of executor
    //Used to change the state and execute a failed callback in then
    let reject = reason => {
      this.reason = reason;
      this.onRejected && this.onRejected(this.reason);
    }

    //Execute executor function
    //Pass in the two functions we defined above as parameters
    //There may be errors when executing the executor function, so try catch 
    try {
      executor(resolve, reject);
    } catch(err) {
      reject(err);
    }
  }

  //Define then function
  //And copy the parameters in then to this.onFulfilled  And this.onRejected
  private then(onFulfilled, onRejected) {
    this.onFulfilled = onFulfilled;
    this.onRejected = onRejected;
  }
}

Well, our first edition is finished, isn’t it very simple.

But what we need to pay attention to here is that,resolveThe execution time of the function needs to be in thethenMethod to register the callback function in theresolveAfter the assignment of the callback function, in fact, it is finished, there is no sense.

There is no problem with the above example becauseResolveIt’s wrapped insetTimeoutWhen the callback function is registered, it will be executed in the next macro task.

You can try itResolvefromsetTimeoutIf you take it out, there will be problems.

There are problems

The implementation of this version is very simple, but there are still several problems

  • The concept of state is not introduced

The concept of state has not been introduced. Now the state can be changed at will, which does not conform to the requirementsPromiseThe rule that the state can only change from the waiting state.

  • Chained calls are not supported

Under normal circumstances, we canPromiseMake chain call:

let p1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    Resolved ('succeeded ');
  }, 1000);
})

p1.then(onResolved1, onRejected1).then(onResolved2, onRejected2)
  • Only one callback function is supported. If there are multiple callback functions, the later one will override the previous one

In this case,onResolved2Will be coveredonResolved1onRejected2Will be coveredonRejected1

let p1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    Resolved ('succeeded ');
  }, 1000);
})

//Register multiple callback functions
p1.then(onResolved1, onRejected1);
p1.then(onResolved2, onRejected2);

Next, let’s go further and solve these problems.

30 minutes, take you to realize a promise that meets the specification

 

Version 2 (implementation of chain call)

In this version, we introduce the concept of state and realize the function of chain call.

Add state

We said abovePromiseThere are three states:pendingresovledrejected, only frompendingToresovledperhapsrejectedAnd when the state changes, the state can no longer be changed.

  • We define an attributestatus: used to record the currentPromiseStatus of
  • To prevent writing errors, we define states as constantsPENDINGRESOLVEDREJECTED
  • At the same time, we will keep itthenThe successful callback of is defined as an array:this.resolvedQueuesAndthis.rejectedQueuesWe can putthenTo solve the third problem mentioned above.
class MyPromise {
  private static PENDING = 'pending';
  private static RESOLVED = 'resolved';
  private static REJECTED = 'rejected';

  constructor (executor: executor) {
    this.status = MyPromise.PENDING;
    // ...

    //Array of successful callbacks to hold then
    this.resolvedQueues = [];
    //Array of failed callbacks to hold then
    this.rejectedQueues = [];

    let resolve = value => {
      //When the status is pending, change the status of promise to success
      //At the same time, traverse the function in the callback array of successful execution, and pass in value
      if (this.status == MyPromise.PENDING) {
        this.value = value;
        this.status = MyPromise.RESOLVED;
        this.resolvedQueues.forEach(cb => cb(this.value))
      }
    }

    let reject = reason => {
      //When the status is pending, change the status of promise to failure
      //At the same time, traverse the function in the callback array of execution failure, and pass in the reason
      if (this.status == MyPromise.PENDING) {
        this.reason = reason;
        this.status = MyPromise.REJECTED;
        this.rejectedQueues.forEach(cb => cb(this.reason))
      }
    }

    try {
      executor(resolve, reject);
    } catch(err) {
      reject(err);
    }
  }
}

Perfect then function

And then we’ll improvethenBefore that, we will directlythenTwo parameters ofonFulfilledandonRejected, directly assigned toPromiseIs used to save instance properties of successful and failed function callbacks.

Now we need to cram these two attributes into two arrays:resolvedQueuesandrejectedQueues

class MyPromise {
  // ...

  private then(onFulfilled, onRejected) {
    //First, determine whether the two parameters are function types, because they are optional parameters
    //When the parameter is not a function type, you need to create a function and assign it to the corresponding parameter
    //This is also the realization of transparent transmission
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason}

    //When the state is waiting, two parameters need to be pushed into the corresponding callback array
    //When the state changes, the function in the callback function is executed
    if (this.status === MyPromise.PENDING) {
      this.resolvedQueues.push(onFulfilled)
      this.rejectedQueues.push(onRejected)
    }

    //If the state is successful, the onfulfilled function is called directly
    if (this.status === MyPromise.RESOLVED) {
      onFulfilled(this.value)
    }

    //If the status is successful, the onrejected function is called directly
    if (this.status === MyPromise.REJECTED) {
      onRejected(this.reason)
    }
  }
}

Some explanations of then function

  • Under what circumstancesthis.statusIt will bependingState, when will it beresolvedstate

This is also related to the event loop mechanism, as shown in the following code:

// this.status  Is pending status
new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(1)
  }, 0)
}).then(value => {
  console.log(value)
})

// this.status  Is the resolved state
new MyPromise((resolve, reject) => {
  resolve(1)
}).then(value => {
  console.log(value)
})
  • What is?transparent transmission

As shown in the following code, whenthenWhen no parameters are passed in,PromiseThe internal default defined method is used to pass the result to the nextthen

let p1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    Resolved ('succeeded ');
  }, 1000);
})

p1.then().then((res) => {
  console.log(res);
})

Because we don’t support chain calls yet, this code will run into problems.

Support chain call

Support chain call, in fact, it is very simple, we only need to givethenFunction returns thethisThis supports chained calls

class MyPromise {
  // ...
  private then(onFulfilled, onRejected) {
    // ...
    return this;
  }
}

Every callthenAfter that, we all go back to the current onePromiseObject, becausePromiseThere is on the objectthenMethod, this time we will simply implementPromiseA simple call to.

This is the time to run the toptransparent transmissionTest code.

However, the above code still has corresponding problems. See the following code:

const p1 = new MyPromise((resolved, rejected) => {
  resolved('resolved');  
});

p1.then((res) => {
  console.log(res);
  return 'then1';
})
.then((res) => {
  console.log(res);
  return 'then2';
})
.then((res) => {
  console.log(res);
  return 'then3';
})

//Predicted output: resolved > then1 > then2
//Actual output: resolved > resolved > resolved

The output deviates from our expectations because wethenReturned inthisOn behalf ofp1Innew MyPromiseAfter that, the status has changed frompendingState change toresolvedIt doesn’t change after that, so in theMyPromiseMediumthis.valueThe value is alwaysresolved

At this time, we have to look at thethenReturn value of the relevant knowledge points.

Then return value

actuallythenWill return a new onePromiseObject.

Take a look at the following code:

//Create a promise
const aPromise = new Promise(function (resolve) {
  resolve(100);
});

//Promise returned by then
var thenPromise = aPromise.then(function (value) {
  console.log(value);
});

console.log(aPromise !== thenPromise); // => true

We can see from the code abovethenMethodPromiseIt’s no longer the originalPromiseHere is the picture (from promise Mini Book)

30 minutes, take you to realize a promise that meets the specification

promiseChain call ofjQueryThere is a difference between linked calls of,jQueryThe object returned by the chain call is still the original onejQueryObject;PromiseIt is more similar to some methods in the array, such assliceAfter each operation, a new value is returned.

Modification code

class MyPromise {
  // ...

  private then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason}

    //The then method returns a new promise
    const promise2 = new MyPromise((resolve, reject) => {
      //Success status, resolve directly
      if (this.status === MyPromise.RESOLVED) {
        //Resolve the return value of the onfulfilled function
        let x = onFulfilled(this.value);
        resolve(x);
      }

      //Failure status, direct reject
      if (this.status === MyPromise.REJECTED) {
        //Reject the returned value of onrejected function
        let x = onRejected(this.reason)
        reject && reject(x);
      }

      //Wait state, put onfulfilled and onrejected into the array, and wait for the callback to execute
      if (this.status === MyPromise.PENDING) {
        this.resolvedQueues.push((value) => {
          let x = onFulfilled(value);
          resolve(x);
        })
        this.rejectedQueues.push((reason) => {
          let x = onRejected(reason);
          reject && reject(x);
        })
      }
    });
    return promise2;
  }
}

//The output results are resolved > then1 > then2

There are problems

At this point, we have completed a simple chain call, but we can only support synchronous chain calls. If we need to use thethenMethod to do other asynchronous operations, the above code GG.

The code is as follows:

const p1 = new MyPromise((resolved, rejected) => {
  'I resolved';  
});

p1.then((res) => {
  console.log(res);
  return new MyPromise((resolved, rejected) => {
    setTimeout(() => {
      resolved('then1');
    }, 1000)
  });
})
.then((res) => {
  console.log(res);
  return new MyPromise((resolved, rejected) => {
    setTimeout(() => {
      resolved('then2');
    }, 1000)
  });
})
.then((res) => {
  console.log(res);
  return 'then3';
})

The code above will directlyPromiseObject is passed directly to the next as an argumentthenFunction, and we actually want to put thisPromiseThe results of the processing are passed on.

30 minutes, take you to realize a promise that meets the specification

 

Third Edition (asynchronous chain call)

Let’s do thispromiseAsynchronous chain call of.

thinking

Have a look firsttheninonFulfilledandonRejectedReturn value:

//Successful function return
let x = onFulfilled(this.value);

//Failed function return
let x = onRejected(this.reason);

As can be seen from the above question,xIt could be aNormal valueIt can also be aPromiseObject, the normal value of the transfer, we in theSecond EditionIt has been solved. What needs to be solved now is whenxReturn onePromiseWhat to do with objects.

Actually, it’s very simplexIt’s aPromiseObject, we need to wait until thePromiseWhen the state changes, it will be executed afterthenFunction, the code is as follows:

class MyPromise {
  // ...

  private then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason}

    //The then method returns a new promise
    const promise2 = new MyPromise((resolve, reject) => {
      //Success status, resolve directly
      if (this.status === MyPromise.RESOLVED) {
        //Resolve the return value of the onfulfilled function
        let x = onFulfilled(this.value);
        resolvePromise(promise2, x, resolve, reject);
      }

      //Failure status, direct reject
      if (this.status === MyPromise.REJECTED) {
        //Reject the returned value of onrejected function
        let x = onRejected(this.reason)
        resolvePromise(promise2, x, resolve, reject);
      }

      //Wait state, put onfulfilled and onrejected into the array, and wait for the callback to execute
      if (this.status === MyPromise.PENDING) {
        this.resolvedQueues.push(() => {
          let x = onFulfilled(this.value);
          resolvePromise(promise2, x, resolve, reject);
        })
        this.rejectedQueues.push(() => {
          let x = onRejected(this.reason);
          resolvePromise(promise2, x, resolve, reject);
        })
      }
    });
    return promise2;
  }
}

Let’s write a new functionresolvePromiseThis function is the core method used to handle asynchronous chain calls. He will judgexThe return value is notPromiseObject, if it is, untilPromiseIf it is a normal value, the value will be changed directlyresovleget out:

const resolvePromise = (promise2, x, resolve, reject) => {
  if (x instanceof MyPromise) {
    const then = x.then;
    if (x.status == MyPromise.PENDING) {
      then.call(x, y => {
        resolvePromise(promise2, y, resolve, reject);
      }, err => {
        reject(err);
      })
    } else {
      x.then(resolve, reject);
    }
  } else {
    resolve(x);
  }
}

Code description

resolvePromise

resolvePromiseFour parameters are accepted:

  • promise2yesthenReturned inpromise
  • xyesthenTwo parameters ofonFulfilledperhapsonRejectedThe type of the returned value of is uncertain. It may be a normal value, and it may bethenableObject;
  • resolveandrejectyespromise2Yes.

Then return value type

WhenxyesPromiseAnd his state isPendingState, ifxIf the execution is successful, it is called recursivelyresolvePromiseThis function willxImplementation results asresolvePromiseThe second parameter is passed in;

If the execution fails, call thepromise2Ofrejectmethod.

 

So far, we are basically a complete onepromiseNext, we need to standardize ourPromise

 

Specification promise

The author of previous versions of the code is basically in accordance with the specification, here mainly talks about a few points that do not conform to the specification.

Specification then (specification 2.2)

theninonFulfilledandonRejectedIt needs to be executed asynchronously, i.e. put into asynchronous task to execute (specification 2.2.4)

realization

We need tothenFunctions in thesetTimeoutWrap it up and put it into a macro task, which involvesjsOfEventLoopYou can read the corresponding articles as follows:

class MyPromise {
  // ...

  private then(onFulfilled, onRejected) {
    // ...
    //The then method returns a new promise
    const promise2 = new MyPromise((resolve, reject) => {
      //Success status, resolve directly
      if (this.status === MyPromise.RESOLVED) {
        //Resolve the return value of the onfulfilled function
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch(err) {
            reject(err);
          }
        })
      }

      //Failure status, direct reject
      if (this.status === MyPromise.REJECTED) {
        //Reject the returned value of onrejected function
        setTimeout(() => {
          try {
            let x = onRejected(this.reason)
            resolvePromise(promise2, x, resolve, reject);
          } catch(err) {
            reject(err);
          }
        })
      }

      //Wait state, put onfulfilled and onrejected into the array, and wait for the callback to execute
      if (this.status === MyPromise.PENDING) {
        this.resolvedQueues.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch(err) {
              reject(err);
            }
          })
        })
        this.rejectedQueues.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason)
              resolvePromise(promise2, x, resolve, reject);
            } catch(err) {
              reject(err);
            }
          })
        })
      }
    });
    return promise2;
  }
}

Using micro task packages

But there is still a problem. We know that in factPromise.thenIt belongs to micro task, and now it is usedsetTimeoutAfter the package is completed, it will become a macro task. See the following example:

var p1 = new MyPromise((resolved, rejected) => {
  resolved('resolved');
})

setTimeout(() => {
  console.log('---setTimeout---');
}, 0);

p1.then(res => {
  console.log('---then---');
})

//Normal promise: then > setTimeout
//Our promise: setTimeout > then

The output order is different because of the currentPromiseYessetTimeoutMacro task wrapped.

We can improve it by using micro tasksonFulfilledonRejectedThe common micro tasks areprocess.nextTickMutationObserverpostMessageWait, we use this onepostMessageRewrite:

// ...
if (this.status === MyPromise.RESOLVED) {
  //Resolve the return value of the onfulfilled function
  //Register a message event
  window.addEventListener('message', event => {
    const { type, data } =  event.data;

    if (type === '__promise') {
      try {
        let x = onFulfilled(that.value);
        resolvePromise(promise2, x, resolve, reject);
      } catch(err) {
        reject(err);
      }
    }
  });
  //Immediately
  window.postMessage({
    type: '__promise',
  }, "http://localhost:3001");
}

// ...

The implementation method is very simple, we monitorwindowOfmessageEvent, and immediately triggers apostMessageEvent, this time actuallythenThe callback function in is already in the micro task queue. If we rerun the example, we can see that the output order changes tothen -> setTimeout

of coursePromiseInternal implementation is certainly not so simple, the author here is just to provide a way of thinking, you are interested to study a wave.

Canonical resolvepromise function (specification 2.3)

Repeated references

Repeated references, whenxandpromise2Is the same, then need to report an error, repeat application. (specification 2.3.1)

Because you will never have a result when you wait for yourself to finish.

const p1 = new MyPromise((resolved, rejected) => {
  'I resolved';  
});

const p2 = p1.then((res) => {
  return p2;
});

30 minutes, take you to realize a promise that meets the specification

Type of X

It can be roughly divided into the following items:

  • 2.3.2: whenxIt’s aPromiseThen waitxAfter changing the state, it is considered to be completed or failed2.3.3BecausePromiseIn fact, it is also athenableObject)
  • 2.3.3: whenxWhen it is an object or function, i.ethenableObject, that’s itx.thenAsthen
  • 2.3.4: whenxWhen it is not an object or a function, it willxAs a parameterresolvereturn.

Let’s mainly look at it2.3.3Just becausePrmiseAlso belong tothenableObject, what is thatthenableWhat about the object?

In short, it means havingthenObject / function of method, allPromiseThe objects are allthenableObject, but not allthenableThe object is notPromiseObject. As follows:

let thenable = {
 then: function(resolve, reject) {
   resolve(100);
 }
}

according toxType of processing:

  • IfxnothenableObject, calling directlyPromise2Ofresolve, willxAs a result of success;
  • WhenxyesthenableObject is calledxOfthenMethod, and call it after successresolvePromiseFunction and executes the resultyAs newxafferentresolvePromiseUntil thisxValue is no longer athenableObject; if it fails, call it directlypromise2Ofreject
if (x != null && (typeof x === 'object' || typeof x === 'function')) {
  if (typeof then === 'function') {
    then.call(x, (y) => {
      resolvePromise(promise2, y, resolve, reject);
    }, (err) => {
      reject(err);
    })
  }
} else {
  resolve(x);
}

Call only once

Specification(Promise/A+ 2.3.3.3.3)Specifies that if theresolvePromiseandrejectPromise, or multiple calls are made to the same parameter, the first call takes precedence and all other calls are ignored, ensuring that only one change of state is performed.

We defined one outsidecalledPlace holder to getthenWhether the function has executed the corresponding function that changes the state. After the function has been executed, it will not be executed again, mainly to meet the specification.

X is a promise object

IfxyesPromiseObject, in fact, it should be implementedresolveAfter the function, it is not executed againrejectFunction, is directly in the currentPromiseThe object is over.

X is the thenable object

WhenxIt’s normalthenableWhen the function is used, it is possible to execute it at the same timeresolveandrejectFunction, that is, it can be executed at the same timepromise2OfresolveFunction sumrejectFunction, but actuallypromise2After the state is changed, the corresponding value will not be changed. In fact, there is no problem. The code is as follows:

//The image of thenable
{
 then: function(resolve, reject) {
   setTimeout(() => {
     I am the resolution of the object;
     Reject ('I'm the reject of thenable object ')
    })
 }
}

Complete resolvepromise

completeresolvePromiseThe functions are as follows:

const resolvePromise = (promise2, x, resolve, reject) => {
  if(x === promise2){
    return reject(new TypeError('Chaining cycle detected for promise'));
  }
  let called;
  if (x != null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      let then = x.then;
      if (typeof then === 'function') {
        then.call(x, y => {
          if(called)return;
          called = true;
          resolvePromise(promise2, y, resolve, reject);
        }, err => {
          if(called)return;
          called = true;
          reject(err);
        })
      } else {
        resolve(x);
      }
    } catch (e) {
      if(called)return;
      called = true;
      reject(e); 
    }
  } else {
    resolve(x);
  }
}

Here we are finished. Are you happy or not!

30 minutes, take you to realize a promise that meets the specification

Finally, we can run our test scriptMyPromiseCompliance with specifications.

test

There are special test scripts(promises-aplus-tests)It can help us test whether the code we write conforms to thePromise/A+Specification of.

But it looks like it can only be testedjsTherefore, the author willtsFile converted tojsFile for testing

Add in the code:

//Code needed to execute test cases
MyPromise.deferred = function() {
  let defer = {};
  defer.promise = new MyPromise((resolve, reject) => {
      defer.resolve = resolve;
      defer.reject = reject;
  });
  return defer;
}

You need to install the following test plug-ins in advance:

#Install test scripts
npm i -g promises-aplus-tests

#Start the test
promises-aplus-tests MyPromise.js

The results were as follows:

30 minutes, take you to realize a promise that meets the specification

Perfect pass. We can see it nextPromiseMore methods are implemented.

 

More ways

Implement the abovePromiseAfter that, it is much easier to write the actual examples and static methods.

Example method

Promise.prototype.catch

realization

In fact, this method isthenMethod of grammar sugar, only need to givethentransmitonRejectedThe parameters areokYes.

private catch(onRejected) {
  return this.then(null, onRejected);
}
example:
const p1 = new MyPromise((resolved, rejected) => {
  resolved('resolved');
})

p1.then((res) => {
  return new MyPromise((resolved, rejected) => {
    setTimeout(() => {
      Rejected ('wrong ');
    }, 1000)
  });
})
.then((res) => {
  return new MyPromise((resolved, rejected) => {
    setTimeout(() => {
      resolved('then2');
    }, 1000)
  });
})
.then((res) => {
  return 'then3';
}).catch(error => {
  console.log('----error', error);
})

//Output after 1s: --- error

Promise.prototype.finally

realization

finally()Method is used to specify whether thePromiseThe object’s final state will be executed.

private finally (fn) {
  return this.then(fn, fn);
}
example
const p1 = new MyPromise((resolved, rejected) => {
  resolved('resolved');
})

p1.then((res) => {
  return new MyPromise((resolved, rejected) => {
    setTimeout(() => {
      Rejected ('wrong ');
    }, 1000)
  });
})
.then((res) => {
  return new MyPromise((resolved, rejected) => {
    setTimeout(() => {
      resolved('then2');
    }, 1000)
  });
})
.then((res) => {
  return 'then3';
}).catch(error => {
  console.log('---error', error);
  return `catch-${error}`
}).finally(res => {
  console.log('---finally---', res);
})

//Output result: -- error error "- >" --- finally --- catch error

 

Static method

Promise.resolve

realization

Sometimes you need to convert existing objects toPromiseObject,Promise.resolve()Methods do that.

static resolve = (val) => {
  return new MyPromise((resolve,reject) => {
    resolve(val);
  });
}
example
MyPromise.resolve({name: 'darrell', sex: 'boy' }).then((res) => {
  console.log(res);
}).catch((error) => {
  console.log(error);
});

//Output result: {Name: "Darrell", sex: "boy"}

Promise.reject

realization

Promise.reject(reason)Method also returns a newPromiseInstance whose status isrejected

static reject = (val) => {
  return new MyPromise((resolve,reject) => {
    reject(val)
  });
}
example
MyPromise.reject Then ((RES) = >{
  console.log(res);
}).catch((error) => {
  console.log(error);
});

//Output result: error

Promise.all

Promise.all() Method is used to add multiplePromiseExample, packaging into a newPromiseexample,

const p = Promise.all([p1, p2, p3]);
  • onlyp1p2p3The state offulfilledpWill become the state offulfilled
  • as long asp1p2p3One of them wasrejectedpThe state ofrejectedAt this time, the first one isrejectThe return value of the instance of thepThe callback function of.
realization
static all = (promises: MyPromise[]) => {
  return new MyPromise((resolve, reject) => {
    let result: MyPromise[] = [];
    let count = 0;

    for (let i = 0; i < promises.length; i++) {
      promises[i].then(data => {
        result[i] = data;
        if (++count == promises.length) {
          resolve(result);
        }
      }, error => {
        reject(error);
      });
    }
  });
}
example
let Promise1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('Promise1');
  }, 2000);
});

let Promise2 = new MyPromise((resolve, reject) => {
  resolve('Promise2');
});

let Promise3 = new MyPromise((resolve, reject) => {
  resolve('Promise3');
})

let Promise4 = new MyPromise((resolve, reject) => {
  reject('Promise4');
})

let p = MyPromise.all([Promise1, Promise2, Promise3, Promise4]);

p.then((res) => {
  //Success in all three is success  
  console.log ('--- succeeded', RES);
}).catch((error) => {
  //As long as there is failure, there is failure 
  console.log ('--- failed', ERR);
});

//Direct output: -- failed promise4

Promise.race

Promise.race()The method is also to wrap multiple promise instances into a new promise instance.

const p = Promise.race([p1, p2, p3]);

as long asp1p2p3There’s one example that takes the lead in changing the state,pThe state of the system changes. The one who took the lead in changingPromiseThe return value of the instance is passed to thepThe callback function of.

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

Examples andallSimilarly, the call is as follows:

// ...

let p = MyPromise.race([Promise1, Promise2, Promise3, Promise4])

p.then((res) => { 
  console.log ('--- succeeded', RES);
}).catch((error) => {
  console.log ('--- failed', ERR);
});

//Direct output: -- successful PROMISE2

Promise.allSettled

This method accepts a set ofPromiseInstance as a parameter, wrapped as a newPromiseexample.

const p = Promise.race([p1, p2, p3]);

Only wait until all of these parameter instances return results, whether they arefulfilledstillrejectedAnd the state of the method can only becomefulfilled

This method is similar toPromise.allWhat’s the differenceallUnable to determine that all requests ended because onallIf one isPromisecoverrejectedpThe state of the state immediately becomesrejectedIt is possible that some asynchronous requests have not finished.

realization
static allSettled = (promises: MyPromise[]) => {
  return new MyPromise((resolve) => {
    let result: MyPromise[] = [];
    let count = 0;
    for (let i = 0; i < promises.length; i++) {
      promises[i].finally(res => {
        result[i] = res;
        if (++count == promises.length) {
          resolve(result);
        }
      })
    }
  });
}
example

Examples andallSimilarly, the call is as follows:

let p = MyPromise.allSettled([Promise1, Promise2, Promise3, Promise4])

p.then((res) => {
  //Success in all three is success  
  console.log ('--- succeeded', RES);
}, err => {
  //As long as there is failure, there is failure 
  console.log ('--- failed', ERR);
})

//Output after 2S: -- successful (4) ["promise 1", "promise 2", "promise 3", "promise 4"]

 

summary

The author of this article takes you step by step to achieve compliancePromise/A+NormativePromiseAfter reading it, I believe you can basically write one by yourselfPromisecoming.

Finally, through a few questions, you can see what you have mastered:

  • PromiseHow to implement callback function return value traversal in?
  • PromiseAfter making a mistake, how to passBubblingPass to the last function that caught the exception?
  • PromiseHow to support chain call?
  • HowPromise.thenPackaged as a micro task?

To be honest, I want a compliment!

30 minutes, take you to realize a promise that meets the specification

 

Reference documents

  • Ruan Yifeng ES6 promise course
  • Promise Mini Book
  • Promise / A + specification document
  • Analyze the inner structure of promise, and implement a complete promise class step by step that can pass all test cases
  • 30 minutes, let you thoroughly understand the promise principle
  • Manually implement a promise satisfying promise aplus tests
  • Interviewer: please use one sentence to describe which JS exceptions can be caught by try catch

 

Sample code

The sample code can be seen here:

  • Promise sample code

Recommended Today

On the theoretical basis of SRE

What is SRE? When I first got into contact with SRE, many people thought that it was a post with full stack capability in Google and could solve many problems independently. After in-depth exploration, it is found that SRE can solve many problems, but there are too many problems. It is difficult for a post […]