preface

There are plenty of good articles on the Promise principle in nuggets. However, the author is always in the state of reading and writing, which is the purpose of the author to write this article. In order to clarify the writing ideas of Promise, I wrote a wave of code from scratch, and it is also convenient for myself to review in the future.

 

The role of the Promise

Promise is a popular solution for asynchronous JavaScript programming. It appears to solve the problem of callback hell, so that users can write asynchronous code through the chain writing method. The author will not introduce the specific usage, you can refer to ruan Yifeng teacher ES6 Promise tutorial.

 

Knowledge before class

Observer model

What is the observer model:

The observer pattern defines a one-to-many dependency that allows multiple observer objects to listen to a target object at the same time. When the state of the target object changes, all observer objects are notified so that they can update automatically.

Promises are implemented based on the observer design pattern. The functions to be performed by the then function are stuffed into the observer array, and when the Promise state changes, all the functions in the observer array are executed.

Event loop mechanism

Implementing promises involves JavaScript’s EventLoop mechanism, as well as the concept of macro and microtasks.

The flow chart of the event loop mechanism is as follows:

Take a look at this code:

console.log(1);

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

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

console.log(6);
Copy the code

If you can’t tell the output immediately, I suggest you check out the event loop. There are many good articles in nuggets.

Promises/A + specification

Promises/A+ is A community norm, and if you want to make A Promise, we need to follow this standard. Then we’ll refine our own promises based on the specification.

 

Promise core points

Before we start writing promises, let’s go over a few important points.

executor

// Create the Promise object x1
// And execute the business logic in executor functions
function executor(resolve, reject){
  // The business logic processes the successful result
  constvalue = ... ; resolve(value);// Failure result
  // const reason = ... ;
  // reject(reason);
}

let x1 = new Promise(executor);
Copy the code

First, a Promise is a class that accepts an executor function that takes two arguments, resolve and Reject. These are functions defined internally by the Promise that change the state and execute the corresponding callback.

Since the Promise itself does not know whether the result will fail or succeed, it simply provides a container for the asynchronous operation. In fact, the control is in the hands of the user, who can call the above two parameters to tell the Promise whether the result will succeed. Both resolve and Reject execute the callback by passing the result of the business logic processing (value/ Reason) as arguments.

Three state

Promise has three states:

  • pending: wait
  • resolvedThat they have been successful
  • rejected: has failed

There are only two possibilities for a Promise state to change: from Pending to Resolved or from Pending to Rejected, as shown below (from the Promise mini-book) :

And the thing to notice is that once the state changes, it doesn’t change any more, and it stays the same. This means that when we call resolve in the executor function, we have no effect when we call reject, and vice versa.

// And execute the business logic in executor functions
function executor(resolve, reject){
  resolve(100);
  // All calls to resolve, reject are invalid,
  // This is resolved and will not change
  reject(100);
}

let x1 = new Promise(executor);
Copy the code

then

Each promise has a then method, which is a callback that needs to be executed when a promise returns a result. This method takes two optional arguments:

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

Below (from the Promise mini-book) :

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

// x1 deferred binding callback onResolve
function onResolved(value){
  console.log(value);
}

// x1 delays binding the callback function onRejected
function onRejected(reason){
  console.log(reason);
}

x1.then(onResolved, onRejected);
Copy the code

 

Write a Promise and outline the process

Here’s a quick overview of how to write a Promise:

Executor has three states

  • new Promise, you need to pass oneexecutorThe executor function, in the constructor,The executor function executes immediately
  • executorThe execution function takes two arguments, respectivelyresolvereject
  • PromiseOnly frompendingrejected, or frompendingfulfilled
  • PromiseOnce confirmed, the state is frozen and does not change

Then method

  • All of thePromiseThere arethenMethod,thenReceives two parameters, respectivelyPromiseSuccessful callbackonFulfilled, and failed callbacksonRejected
  • If the callthenWhen,PromiseIf yes, run the commandonFulfilledAnd willPromiseIs passed as a parameter; ifPromiseIf failed, executeonRejectedAnd willPromiseThe cause of failure is passed in as a parameter; ifPromiseThe state ispending, you need toonFulfilledonRejectedFunctions are stored, wait for the state to be determined, and then execute the corresponding functions (observer mode)
  • thenThe parameters of theonFulfilledonRejectedYou don’t have to,Promise Value penetration is possible.

Chain calls and processes the then return value

  • PromisecanthenMany times,PromisethenMethod returns a new onePromise.
  • ifthenReturns a normal value, which would render the result (value) as an argument, passed to the nextthenSuccessful callback (onFulfilled)
  • ifthenThrows an exception, then the exception (reason) as an argument, passed to the nextthenFailed callback (onRejected)
  • ifthenIt returns onepromiseOr otherthenableObject, so you have to wait for thispromiseWhen the execution is complete,promiseIf it works, move on to the nextthenThe successful callback; If you fail, move on to the nextthenThe failed callback.

The above is the general implementation process, if you feel confused, it doesn’t matter, as long as you have a general impression, we will talk about one by one.

So let’s start with the simplest implementation.

 

First edition (start with a simple example)

Let’s start with a simple version that does not yet support state, chained calls, and only calls to a then method.

To a 🌰

let p1 = new MyPromise((resolve, reject) = > {
    setTimeout((a)= > {
      resolved('It worked');
    }, 1000);
})

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

The example is simple: return success after 1s and print then.

implementation

We define a MyPromise class, and we write code in it as follows:

class MyPromise {
  // ts interface definition...
  constructor (executor: executor) {
    // Used to save the value of resolve
    this.value = null;
    // Saves the reject value
    this.reason = null;
    // Used to save successful callbacks to THEN
    this.onFulfilled = null;
    // Used to save failed callbacks for THEN
    this.onRejected = null;

    // Executor's resolve parameter
    // Used to change the state and perform a successful callback in THEN
    let resolve = value= > {
      this.value = value;
      this.onFulfilled && this.onFulfilled(this.value);
    }

    // Executor reject parameter
    // Used to change state and perform failed callbacks in THEN
    let reject = reason= > {
      this.reason = reason;
      this.onRejected && this.onRejected(this.reason);
    }

    // Execute the executor function
    // Pass in the two functions we defined above as arguments
    // It is possible to make an error while executing the executor function, so you need to try and catch it
    try {
      executor(resolve, reject);
    } catch(err) { reject(err); }}// Define the then function
  // And copy the parameters in then to this. onpity and this.onRejected
  private then(onFulfilled, onRejected) {
    this.onFulfilled = onFulfilled;
    this.onRejected = onRejected; }}Copy the code

Ok, so we’re done with the first version, isn’t that easy?

Resolve is executed after the then callback is registered and then before the assignment callback is executed.

The above example is fine because resolve is packaged in setTimeout and will be executed by the next macro task, at which point the callback function is registered.

Try taking resolve out of setTimeout, and this will cause problems.

There is a problem

This version of the implementation is very simple, there are a few problems:

  • The concept of states is not introduced

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

  • Chain calls are not supported

Normally we would make a chain call to a Promise:

let p1 = new MyPromise((resolve, reject) = > {
  setTimeout((a)= > {
    resolved('It worked');
  }, 1000);
})

p1.then(onResolved1, onRejected1).then(onResolved2, onRejected2)
Copy the code
  • Only one callback is supported, and if more than one callback exists, the last one overwrites the previous one

In this example, onResolved2 overrides onResolved1 and onRejected2 overrides onRejected1.

let p1 = new MyPromise((resolve, reject) = > {
  setTimeout((a)= > {
    resolved('It worked');
  }, 1000);
})

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

Let’s go one step further and solve these problems.

 

Version 2 (Implementing chain calls)

In this version, we introduce the concept of states and implement the function of chain calls.

Combined with state

Promise has three states: Pending, Resovled, and Rejected. You can only change the state from pending to Resovled or Rejected.

  • We define a propertystatus: Records the current informationPromiseThe state of the
  • To prevent writing errors, we define the state as a constantPENDING,RESOLVED,REJECTED.
  • In the meantime we’re going to savethenThe success callback is defined as an array:this.resolvedQueuesthis.rejectedQueuesWe can putthenTo solve the third problem we mentioned above.
class MyPromise {
  private static PENDING = 'pending';
  private static RESOLVED = 'resolved';
  private static REJECTED = 'rejected';

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

    // An array of successful callbacks for then
    this.resolvedQueues = [];
    // An array of failed callbacks for then
    this.rejectedQueues = [];

    let resolve = value= > {
      // When the state is pending, change the state of the promise to success
      // Pass in value while iterating over the function in the successful callback array
      if (this.status == MyPromise.PENDING) {
        this.value = value;
        this.status = MyPromise.RESOLVED;
        this.resolvedQueues.forEach(cb= > cb(this.value))
      }
    }

    let reject = reason= > {
      // When the state is pending, change the state of the promise to a failed state
      // Pass reason through the function in the failed callback array
      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); }}}Copy the code

Perfect then function

Next, we will improve the method in THEN. Before, we will directly assign the two parameters of THEN, onFulfilled and onRejected, to the instance attribute of Promise, which is used to save the successful and failed function callback.

Now we need to stuff these two attributes into two arrays: resolvedQueues and rejectedQueues.

class MyPromise {
  // ...

  private then(onFulfilled, onRejected) {
    // First check whether the two arguments are function types, since they are optional
    When an argument is not a function type, you need to create a function to assign to the corresponding argument
    // This implements pass-through
    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 inserted into the corresponding callback array
    // Execute the function in the callback function after the state changes
    if (this.status === MyPromise.PENDING) {
      this.resolvedQueues.push(onFulfilled)
      this.rejectedQueues.push(onRejected)
    }

    // The state is successful, so call ondepressing function directly
    if (this.status === MyPromise.RESOLVED) {
      onFulfilled(this.value)
    }

    // If the status is successful, call onRejected directly
    if (this.status === MyPromise.REJECTED) {
      onRejected(this.reason)
    }
  }
}
Copy the code

Some instructions for then functions

  • Under what circumstancesthis.statusWill bependingState, under what circumstances can it beresolvedstate

This is also related to the event loop mechanism, as follows:

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

// This. Status is Resolved
new MyPromise((resolve, reject) = > {
  resolve(1)
}).then(value= > {
  console.log(value)
})
Copy the code
  • What is passthrough

As shown in the following code, when no arguments are passed to the then, the Promise passes the result to the next THEN using the methods defined by the internal default.

let p1 = new MyPromise((resolve, reject) = > {
  setTimeout((a)= > {
    resolved('It worked');
  }, 1000);
})

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

Because we don’t currently support chained calls, this code will run in trouble.

Support for chain calls

Support for chain calls is simple, we just need to return this at the end of the then function, which supports chain calls:

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

After each call to THEN, we return the current Promise object, since the then method exists on the Promise object, and we simply implement the simple call to the Promise.

This is the time to run the pass-through test code above.

But the above code still has corresponding problems, look at 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';
})

Resolved -> then1 -> then2
Resolved -> resolved -> resolved
Copy the code

The output is different from our expectation, because the this returned in then represents P1. After new MyPromise, the state has changed from pending to Resolved and will not change again. So this. Value in MyPromise will always be Resolved.

At this point, we need to look at the return value of then.

Then the return value

In fact, then always returns a new Promise object.

Take a look at the following code:

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

// then returns the promise
var thenPromise = aPromise.then(function (value) {
  console.log(value);
});

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

From the code above we can see that the Promise returned by the then method is no longer the original Promise, as shown below (from the Promise mini-book) :

Promise’s chain call is different from the jQuery chain call. The jQuery chain call returns the same jQuery object. Promises are more like array methods, such as slice, that return a new value after each operation.

Modified 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) = > {
      // Successful state, directly resolve
      if (this.status === MyPromise.RESOLVED) {
        // The ondepressing function will return the value, resolve out
        let x = onFulfilled(this.value);
        resolve(x);
      }

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

      // This is a big pity, onFulfilled, onRejected into the array, waiting for the callback to execute
      if (this.status === MyPromise.PENDING) {
        this.resolvedQueues.push((value) = > {
          let x = onFulfilled(value);
          resolve(x);
        })
        this.rejectedQueues.push((reason) = > {
          letx = onRejected(reason); reject && reject(x); }}}));returnpromise2; }}Resolved -> then1 -> then2
Copy the code

There is a problem

At this point we are done with simple chain calls, but only synchronous chain calls are supported. If we need to do other asynchronous operations in the THEN method, the above code will be GG.

The following code:

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

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

The above code passes the Promise object as an argument directly to the next THEN function, when we actually want to pass the result of processing the Promise.

 

3rd edition (Asynchronous chain Call)

In this release we implement the asynchronous chain call of Promise.

Train of thought

OnFulfilled and onRejected will return the values of onFulfilled and onFulfilled.

// The successful function returns
let x = onFulfilled(this.value);

// Failed function returns
let x = onRejected(this.reason);
Copy the code

As can be seen from the above problems, x can be either a common value or a Promise object. The transmission of common values has been solved in the second version. What we need to solve now is how to handle when X returns a Promise object.

When x is a Promise object, we need to wait until the returned Promise state changes, and then execute the subsequent then function. 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) = > {
      // Successful state, directly resolve
      if (this.status === MyPromise.RESOLVED) {
        // The ondepressing function will return the value, resolve out
        let x = onFulfilled(this.value);
        resolvePromise(promise2, x, resolve, reject);
      }

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

      // This is a big pity, onFulfilled, onRejected into the array, waiting for the callback to execute
      if (this.status === MyPromise.PENDING) {
        this.resolvedQueues.push((a)= > {
          let x = onFulfilled(this.value);
          resolvePromise(promise2, x, resolve, reject);
        })
        this.rejectedQueues.push((a)= > {
          let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); }}}));returnpromise2; }}Copy the code

We will write a new function resolvePromise, which is used to handle the core method of asynchronous chain calls. It will determine if the value returned by x is a Promise object. If so, the state will change after the Promise returns. Just resovle the value 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); }}Copy the code

Code instructions

resolvePromise

The resolvePromise accepts four parameters:

  • promise2thenIn returnpromise;
  • xthenTwo parameters ofonFulfilledoronRejectedThe type is indeterminate. It could be a normal value. It could bethenableObject;
  • resolverejectpromise2.

Then returns the value type

When x is a Promise and its state is Pending, if x is successfully executed, the resolvePromise function is recursively called and the result of x execution is passed in as the second parameter of resolvePromise.

If the execution fails, the reject method of promise2 is called directly.

 

Here we have basically A complete promise. Next we need to standardize our Promises based on Promises/A+.

 

Specification Promise

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

Specification THEN (Specification 2.2)

Ondepressing and onRejected need to be performed asynchronously in then, that is, put them into an asynchronous task (Specification 2.2.4).

implementation

We need to wrap the then function with setTimeout and put it into a macro task. This involves js EventLoop.

class MyPromise {
  // ...

  private then(onFulfilled, onRejected) {
    // ...
    The then method returns a new promise
    const promise2 = new MyPromise((resolve, reject) = > {
      // Successful state, directly resolve
      if (this.status === MyPromise.RESOLVED) {
        // The ondepressing function will return the value, resolve out
        setTimeout((a)= > {
          try {
            let x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch(err) { reject(err); }})}// Reject
      if (this.status === MyPromise.REJECTED) {
        // reject the return value of the onRejected function
        setTimeout((a)= > {
          try {
            let x = onRejected(this.reason)
            resolvePromise(promise2, x, resolve, reject);
          } catch(err) { reject(err); }})}// This is a big pity, onFulfilled, onRejected into the array, waiting for the callback to execute
      if (this.status === MyPromise.PENDING) {
        this.resolvedQueues.push((a)= > {
          setTimeout((a)= > {
            try {
              let x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch(err) { reject(err); }})})this.rejectedQueues.push((a)= > {
          setTimeout((a)= > {
            try {
              let x = onRejected(this.reason)
              resolvePromise(promise2, x, resolve, reject);
            } catch(err) { reject(err); }})})}});returnpromise2; }}Copy the code

Use microtask packages

Promise. Then is a microtask. Now, when you use the setTimeout package, it becomes a macro task.

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

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

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

// Normal Promise: then -> setTimeout
// Our Promise: setTimeout -> then
Copy the code

The output order is different because promises are now wrapped in the setTimeout macro task.

We can improve ondepressing and onRejected by using micro-tasks. Common micro-tasks include Process. NextTick, MutationObserver, postMessage, etc. Let’s rewrite this using postMessage:

// ...
if (this.status === MyPromise.RESOLVED) {
  // The ondepressing function will return the value, resolve out
  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); }}});// Execute immediately
  window.postMessage({
    type: '__promise',},"http://localhost:3001");
}

// ...
Copy the code

The implementation is simple. We listen for the Message event of the window and immediately trigger a postMessage event. The then callback is already in the microtask queue. You can see that the order of output changes to then -> setTimeout.

Of course, the internal implementation of Promise is certainly not so simple, the author here just provides a way of thinking, you can go to study a wave of interest.

Specification resolvePromise function (Specification 2.3)

Repeated references

Repeat reference, when x and promise2 are the same, then an error is reported and repeated. (Specification 2.3.1) because waiting for yourself to finish is never productive.

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

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

The type of x

Roughly divided into the following several:

  • 2.3.2: whenxIs aPromiseThen waitxCompleted or failed only after changing state (this also belongs to this category2.3.3Because thePromiseIn fact, it’s also athenableObject)
  • 2.3.3: whenxIs an object or a function, i.ethenableObject, then therex.thenAs athen
  • 2.3.4: whenxNot an object, or a function when directly willxAs a parameterresolveTo return.

Let’s focus on 2.3.3, since Prmise is also a Thenable object, so what is a Thenable object?

This is simply an object/function with then methods. All Promise objects are Thenable objects, but not all Thenable objects are not Promise objects. As follows:

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

Handle according to the type of x:

  • If x is not a Thenable object, call resolve of Promise2 as the result of success.

  • When x is a thenable object, the then method of X will be called. After success, the resolvePromise function will be called, and the execution result y will be passed to the resolvePromise as the new X. Until the x value is no longer a Thenable object; If it fails, call Promise2 Reject.

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);
}
Copy the code

Only called once

The specification (Promise/A+ 2.3.3.3.3) states that if both resolvePromise and rejectPromise are called, or if 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 define a placeholder called outside to see if the then function executes the corresponding state-changing function, and then does not execute it again, mainly to satisfy the specification.

X is the Promise object

If x is a Promise, resolve is rejected, and the Promise is finished.

X is the Thenable object

When x is a normal Thenable function, it is possible for x to execute resolve and reject simultaneously, that is, resolve and Reject of promise2 can be executed simultaneously. I’m not going to change the values anymore. In fact, there is no problem, the following code:

/ / thenable to like
{
 then: function(resolve, reject) {
   setTimeout((a)= > {
     resolve('I am resolve of thenable object');
     reject('I'm Thenable reject')}}}Copy the code

The complete resolvePromise

The complete resolvePromise function looks like this:

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); }}Copy the code

Here is done, open not happy, xing not excited!

Finally, we can run a test script to see if our MyPromise meets the specification.

test

There are special scripts (Promises -aplus-tests) to help us test whether the code we write meets the Promise/A+ specification.

But it seems that only JS files can be tested, so the author will ts files into JS files, test

Add:

// The code needed to execute the test case
MyPromise.deferred = function() {
  let defer = {};
  defer.promise = new MyPromise((resolve, reject) = > {
      defer.resolve = resolve;
      defer.reject = reject;
  });
  return defer;
}
Copy the code

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

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

#To begin testing
promises-aplus-tests MyPromise.js
Copy the code

The results are as follows:

With perfect approval, we can now look at more ways to implement Promise.

 

More methods

After implementing the Promise above, it’s relatively easy to write examples and static methods.

Instance methods

Promise.prototype.catch

implementation

This method is the syntactic sugar of the THEN method, just pass the onRejected parameter to the then.

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

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

// after 1s: ----error Error
Copy the code

Promise.prototype.finally

implementation

The finally() method is used to specify actions that will be performed regardless of the final state of the Promise object.

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

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

/ / output results: - error mistake "- >" "- finally - catch - a mistake
Copy the code

 

A static method

Promise.resolve

implementation

Sometimes you need to turn an existing object into a Promise object, and the promise.resolve () method does this.

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

{name: "Darrell ", sex: "boy"}
Copy the code

Promise.reject

implementation

The promise.Reject (Reason) method also returns a new Promise instance with a state of Rejected.

static reject = (val) = > {
  return new MyPromise((resolve,reject) = > {
    reject(val)
  });
}
Copy the code
example
MyPromise.reject("Something went wrong.").then((res) = > {
  console.log(res);
}).catch((error) = > {
  console.log(error);
});

// Output result: error
Copy the code

Promise.all

The promise.all () method is used to wrap multiple Promise instances into a new Promise instance,

const p = Promise.all([p1, p2, p3]);
Copy the code
  • onlyp1,p2,p3The state of theta becomesfulfilled.pWill becomefulfilled;
  • As long asp1,p2,p3One of them wasrejected.pThe state of theta becomes thetarejectedAt this time, the first to berejectIs passed topThe callback function of.
implementation
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); }); }}); }Copy the code
example
let Promise1 = new MyPromise((resolve, reject) = > {
  setTimeout((a)= > {
    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) = > {
  // If all three are successful, they are successful
  console.log('-- It worked ', res);
}).catch((error) = > {
  // If there is a failure, there is a failure
  console.log('-- failed ', err);
});

// Direct output: -- failed Promise4
Copy the code

Promise.race

The promise.race () method again wraps multiple Promise instances into a new Promise instance.

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

If one instance of P1, P2, and P3 changes state first, then the state of P changes. The return value of the first changed Promise instance is passed to p’s callback.

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

The example, like all, is called as follows:

// ...

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

p.then((res) = > { 
  console.log('-- It worked ', res);
}).catch((error) = > {
  console.log('-- failed ', err);
});

// Direct output: -- successful Promise2
Copy the code

Promise.allSettled

This method takes a set of Promise instances as parameters and wraps them into a new Promise instance.

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

Only after all these parameter instances return the result, whether this is a pity or Rejected, and the state of the method can only become a pity.

The difference between this method and promise. all is that all requests cannot be confirmed. In all, if a Promise is rejected, P changes to Rejected immediately, and some asynchronous requests may not be completed.

implementation
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); }}}})); }Copy the code
example

The example, like all, is called as follows:

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

p.then((res) = > {
  // If all three are successful, they are successful
  console.log('-- It worked ', res);
}, err= > {
  // If there is a failure, there is a failure
  console.log('-- failed ', err);
})

["Promise1", "Promise2", "Promise3", "Promise4"]
Copy the code

 

conclusion

In this article, I take you step by step to achieve the Promise in line with the Promise/A+ specification, after reading I believe that you can basically write A Promise independently.

Finally, you can see how well you have mastered the following questions:

  • PromiseHow is callback function return value penetration implemented in?
  • PromiseAfter the error, how do you get throughThe bubblingPass it to the last function that catches the exception?
  • PromiseHow do you support chained calls?
  • How to set thePromise.thenPackaged as a microtask?

Honestly, I want a thumbs up!

 

Reference documentation

  • Ruan ES6 Promise tutorial
  • Promise a book
  • Promises/A+ Specification document
  • Analyze the internal structure of Promise, step by step to achieve a complete Promise class that can pass all Test cases
  • 30 minutes to make sure you understand the Promise principle
  • Manually implement a Promise that satisfies Promises – aplus-Tests
  • Interviewer: Please describe in one sentence what JS exceptions can be caught by try catch

 

The sample code

Sample code can be seen here:

  • Promise sample code