ES6 version Promise implementation, give you a different experience

Abstract: A long, long time ago, Promise did not come into this world. There were siege lions in the forest at that time, and they suffered the ravages of callback hell (callback Pyramid). Until one day, an anonymous agent, code-named Promise, rescued them from callback hell. Since then, many people have gone on the road to find Promise, and so have I…

Friendship reminder: this article uses ES6 to realize the Promise, not children shoes please imagine! What? This classmate you unexpectedly do not know ES6, good, after school please do not go, we separate exchange……

Call back to Hell VS Promise

Take fs (node’s core package) for example. Let’s say we need to request data from both A.tuck and B.tuck, and then operate on that data. This demand is often encountered in our development oh!

  • Used to be callback hell
let fs = require('fs');
let arr = [];
fs.readFile('a.txt'.'utf8'.function(err,data){
  arr.push(data);
  fs.readFile('b.txt'.'utf8'.function(err,data){ arr.push(data); Console. log(arr); console.log(arr); })})Copy the code
  • Now the Promise of
let fs = require('fs');
function read(url,coding){// First we promise fs.readfile ()return new Promise((resolve,reject)=>{
    fs.readFile(url,coding,function(err,data){
      if(err) reject(err);
      resolve(data);
    })
  })
}
Promise.all([read('a.txt'.'utf8'),read('b.txt'.'utf8'). Then (data=>{// We can directly manipulate the data of the requested two files,Promise also helpfully returns an array console.log(data); })Copy the code

Promise’s war with Callback Hell, by contrast, wasn’t even on the same level from the start. Callback Hell sounded powerful, but it wasn’t. Promise’s heart should be like this:

Promises come true on their own

At this point, I’m sure you’re wondering what the core implementation of Promise is. Next, please don’t close your eyes, look here, look here! I’ll tell you how MY path to finding Promise went dark. (From this title, laughing out of pigs)

1. Promise class encapsulation

At first, I found out that Promise could be new, indicating that Promise came from a class, which was a valuable clue. (Everybody knows that, you’re telling me)

Class Promise {constructor(executor) {// Executor is the new Promise parameter this.status ='pending'; // Save state this.reason = undefined; This. value = undefined; // Successful resultslet resolve = (value)=> {
      if(this.status === 'pending'){
        this.status = 'resolved'; this.value = value; }}let reject = (reason)=>{
      if(this.status === 'pending'){
        this.status = 'rejected'; this.reason = reason; } } try { executor(resolve, reject); } catch (e) {reject(e); }} // Define on the instancethenMethod,thenAll calls are made asynchronouslythen(onFulfilled, onRejected) {
    if(this.status === 'resolved'Onprogressively onprogressively (this.value); // Perform onprogressively onprogressively (this.value); }if(this.status === 'rejected'){// This is a big pity (this. Reason); }}}Copy the code

How could this be written out with only one clue? It’s so confusing! Don’t worry, listen to me slowly:

  • executorExecutor execution is synchronous. Why try executor execution when the default is newthrow new Error('error')“, go reject
  • resolve, rejectTo define theexecutortheresolveSuccessful callback function andrejectFailed callback function with two arguments
  • reason,value: represents the successful return value and failure cause respectively
  • status: save thePromiseThe three states ofpending(waiting state),fulfilled(Success state),rejected(Failed state)
    • When apromiseBe in the state ofpendingPhi, it can transition to phifulfilledorejected
    • When a promise state is infulfilledOr when therejectedCannot transition to any other state
  • thenFunction:PromiseIt can be called by chainthenThe function is defined inPromiseThe prototype of the classPrototypeOn the.

Now that we’ve managed to deal with promises in sync, don’t you think you’ve tracked down the ultimate Promise power? (Smoke a cigarette, calm down the restless mood)

2. Asynchronous implementation of Promise

The Promise executor executes synchronously, and it doesn’t wait for us to do anything about it. Based on the above code, we add the following lines:

class Promise { constructor(executor) { this.onResolvedCallbacks = []; // Save the successful callback this.onRejectedCallbacks = []; // Save the failed callbacklet resolve = (value)=> {
      if(this.status === 'pending'){
        this.status = 'resolved'; this.value = value; this.onResolvedCallbacks.forEach(fn=>fn()); }}let reject = (reason)=>{
      if(this.status === 'pending'){
        this.status = 'rejected'; this.reason = reason; this.onRejectedCallbacks.forEach(fn=>fn()); }}}then(onFulfilled, onRejected) { 
    if(this.status === 'pending'){ this.onResolvedCallbacks.push(()=>{ onFulfilled(this.value); }); this.onRejectedCallbacks.push(()=>{ onRejected(this.reason); }); }); }}}Copy the code

When asynchronous code occurs, the status of status is still pending. We can store the successful and failed callbacks of then function first. After the asynchronous code is executed, the status of status changes, and then execute the saved callbacks in sequence.

If you feel that you have basically mastered the Promise implementation, you can only say that you know nothing about the core power of Promise. (Cut the crap and write) All right, everyone!

3. Implementation of the chain call of Promise

Before we start, let’s take a look at the following code:

// The Promise here is wrapped in ES6, not implemented by ourselveslet promise = new Promise((resolve,reject)=>{ 
  resolve('123');
})
let promise2 = promise.then((data)=>{
  throw new Error('error'); }) promise2.then((data)=>{ console.log(data); },(err)=>{ console.log(err); // error})Copy the code

The return value is not this, because promises cannot be called successfully and cannot fail again. Therefore, the return value of promise2 after the then function is executed is a new promise instance. (Not like jQuery’s chain call)

Promise’s constructor code doesn’t need to change, just rewrap the THEN function:

thenThis is a big pity, onRejected) {// onFulfilled and onRejected may not be fulfilled = typeof onFulfilled === = this is a big pity'function' ? onFulfilled : value=>value;
    onRejected = typeof onRejected === 'function' ? onRejected : (err)=>{throw err};
    letpromise2; // Each call is requiredthenPromise2 = new promise ((resolve, reject)=>{if(this.status === 'resolved') {setTimeout(()=>{
          try {
            letx = onFulfilled(this.value); ResolvePromise (promise2,x,resolve, reject); } catch (e) { reject(e); }}}, 0)if(this.status === 'rejected') {setTimeout(()=>{
          try {
            letx = onRejected(this.reason); resolvePromise(promise2,x,resolve, reject); } catch (e) { reject(e); }}}, 0)if(this.status === 'pending'){
        this.onResolvedCallbacks.push(()=>{
          setTimeout(()=>{
            try {
              letx = onFulfilled(this.value); resolvePromise(promise2,x,resolve, reject); } catch (e) { reject(e); }}}, 0)); this.onRejectedCallbacks.push(()=>{setTimeout(()=>{
            try {
              letx = onRejected(this.reason); resolvePromise(promise2,x,resolve, reject); } catch (e) { reject(e); }}}, 0)); }})return promise2;
  }
Copy the code
  • onFulfilled,onRejected: What needs to be done when there is no transmission
  • promise2:thenThe return value of the function is a new Promise
  • setTimeout: Promise/A+specification) requirementsthenThe function must be asynchronous, and of course the native Promise implementation is not a setTimeout, but a microtask
  • resolvePromise: encapsulationresolvePromiseMethod, when a success or failure function in the then function returns a value x that may still be a promise

The resolvePromise method defined:

let resolvePromise = (promise2,x,resolve, reject)=>{
  letcalled; // promise2 returns the same value as the functionif(promise2 === x){
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
  }
  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); // Recurse until y is a normal value},(err)=>{if(called) return;
          called = true; reject(err); })}else{ // thenIf phi is a constantif(called) return;
        called = true;
        resolve(x);
      }
    } catch (e) {
      if(called) return;
      called = true; reject(e); }}else{// if x is a constantif(called) return;
    called = true; resolve(x); }}Copy the code
  • Four parameters:promise2(The return value of the then function is a new Promise)x(return value of successful and failed functions in then)resolve(Resolve of promise2)reject(Promise2 reject)
  • calledIncreased:calledJudge, prevent multiple calls, because the logic here is not only yours, but somebody else’s, somebody else’spromiseThe call may fail as well as succeed
  • let then = x.then:xMaybe it’s still apromiseSo let’s do thisPromiseperform

This brings us to the core power of promise. Come, let’s have a little celebration:

3, Promise and so on method implementation

Of course, we have already begun to understand the core strength of Promise. In our development process, in addition to the THEN method, we will also use some of its other commonly used methods, just like a seasoned secret agent, you can use a knife, but also a gun. We define them on the Promise class:

static resolve(value){
    return new Promise((resolve,reject)=>{
      resolve(value);
    })
  }
  static reject(reason){
    return new Promise((resolve,reject)=>{
      reject(reason);
    })
  }
  static all(promises){
    return new Promise((resolve,reject)=>{
      let arr = [];
      let i = 0;
      let processData = (index,data)=>{
        arr[index] = data;
        if(++i === promises.length){ resolve(arr); }}for(let i = 0; i< promises.length; i++){
        promises[i].then(data=>{
          processData(i,data);
        },reject);
      }
    })
  }
  static race(promises){
    return new Promise((resolve,reject)=>{
      for(let i = 0; i< promises.length; i++){
        promises[i].then(resolve,reject);
      }
    })
  }
  catch(onRejected){
    return this.then(null,onRejected);
  }
Copy the code

Resolve, Reject, All, and Race are all familiar with the catch prototype, so I won’t go into any more details.

Because, I really can’t make this up, I have more important things to do:

Conclusion: spent a long time to write this article, if this article makes you more or less some harvest, please don’t be stingy with your praise (point a praise before leaving, little brother little sister), if there are wrong places to write, also hope you can not stingy advice, thank you very much! Original article, reproduced please indicate the source!