Hello ~ Dear audience, I haven’t written this article for some time. I’m busy with work handover recently, so I really haven’t had time to summarize. Here’s a simple implementation of es6 Promise.

In fact, similar articles have been many, many gods have wonderful implementation of this. However, their digestion is the best, in the article actually encountered a lot of pits, such as the point of this, which Promise is the callback. This article tries to explain the above problems, and strive to let everyone better grasp the Promise.

Publish/subscribeA simple implementation of

JS asynchronous programming I believe we are familiar with, before Promise appeared, mainly use publish/subscribe mode, callback function and other ways to achieve asynchronous. From what I understand, promises in particular use a publish/subscribe model, calling resolve() when appropriate, just like a publish event, and the subscriber’s callback function is registered. So let’s start with a simple publish/subscribe model:

functionPromise(fn) {// Subscriber callback array this.callbacks = []; // Execute the callback this.callbacks. ForEach (fn => {fn(val); // Execute the callback this.callbacks. })}; // Pass resolve as an argument to fn to decide when to publish the message fn(resolve); }Copy the code

For those familiar with publish/subscribe, this code will feel particularly familiar. For those not so familiar, consider this scenario: When you go to the mall to buy clothes, just did not have your code number, so you leave the phone (push into the calBacks array), so the sales sister when available to inform me to come back to buy (passed fn call resolve, publish information). Typical Hollywood principle implemented.

Missing from the above code is a sales girl recording a call, which pushes some functions into the callBacks implementation. We could add code like this:

Promise.prototype.then = function(successCallback) {
  this.callBacks.push(successCallback);
};Copy the code

Give it a try:

const p = new Promise(function(res) {
  setTimeout(() => {
    res(123);
  }, 1000);
});

p.then(function(val) {
  console.log(val);
})Copy the code

After executing the code in the browser, you can see 123 printed in the console 1s later.

Add state

The publish/subscribe Promise implementation worked fine, but it lacked the state of everyday use Promise: If I already had my size when I went to buy the clothes, there was no point in leaving the phone number for the salesgirl to call me back. I would have bought them on the spot. Therefore, we introduce the state. For simple implementation, we introduce the state with no code number: pending and the state that can be purchased immediately: FULFILL.

First modify the then method and execute the callback immediately if there is a code number (such as fulfill) :

Promise.prototype.then = function(successCallback) {// If promise has decided that long, execute the callback immediatelyif(this.status === 'fulfill'){
    successCallback();
  }else{ this.callBacks.push(successCallback); }};Copy the code

We can’t pass the Promise value into successCallback, which is fine. We’ll just define a _val constructor to record the Promise value.

functionPromise(fn) {// Subscriber callback array this.callbacks = []; // The state of the promise must start with pending this.status ='pending'; This._val = null; Const resolve = val => {// Switch the state this.status ='fulfill'; // This._val = val; // Execute the callback this.callbacks. ForEach (fn => {fn(val); })}; // Pass resolve as an argument to fn to decide when to publish the message fn(resolve); } Promise.prototype.then =function(successCallback) {// If promise has decided that long, execute the callback immediately or push it into the callback array firstif (this.status === 'fulfill') {
    successCallback(this._val);
  } else{ this.callBacks.push(successCallback); }};Copy the code

Test it out:

const p = new Promise(function(res) {
  setTimeout(() => { res(123); }, 1000)}); p.then(function(val) {
  console.log(val);
});

setTimeout(() => {
  p.then(function(val) { console.log(val); }); }, 2000).Copy the code

The console prints 123 one second later, and again one second later. So far so good~

Chain calls

In ES6, promises can be invoked chained, which is not yet possible. In general, the simplest and most direct way to do a chained call is to return this after the then call; However, in the implementation of the Promise, this is a real trap. Imagine if I kept saying return this; So how do you manage the array of callbacks? According to the Promise/A+ specification, each call to THEN returns A new Promise. By constantly returning new promises, the array of callbacks is easy to manage! Let’s implement it this way:

   functionPromise(fn) {// Subscriber callback array this.callbacks = []; // The state of the promise must start with pending this.status ='pending'; This._val = null; Const resolve = val => {// Switch the state this.status ='fulfill'; // This._val = val; // Execute the callback this.callbacks. ForEach (fn => {fn(val); })}; // Pass resolve as an argument to fn to decide when to publish the message fn(resolve); } Promise.prototype.then =function(successCallback) {// Create a new higher-order function to processreturnResolve const _handleSuccessCallback => val => {//successCallback is a promise instance callthenSuccessCallback passed in when called is notreturnThe outgoing Promise instance is invokedthenConst result = successCallback(val); // If result is a promise, add onethenIn performingreturnGo out promise's resolveif(result && result instanceof Promise) { result.then(_val => { resolve(_val); })}else{ resolve(result); }}; // Cache this const that = this for easy understanding;return new Promise(function(resolve) {// Execute the callback immediately if the promise has been so long or push it into the callback array firstif (that.status === 'fulfill') {
         _handleSuccessCallback(resolve)(that._val);
       } else{ that.callBacks.push(_handleSuccessCallback(resolve)); }})};Copy the code

The only difference is that then returns a new Promise. The problem is that the _handleSuccessCallback method returns a Promise.

Promise.prototype.then takes a function that can either be synchronous and return a value after processing (or not even return), or asynchronous and return a Promise instance, Resolve is called when appropriate, passing the value asynchronously for use by subsequent THEN. Synchronous is ok, but the key is how do you solve the asynchronous problem? Remember that returned Promise instances can have lots of “Then” s!

In fact, no matter how many “THEN” s there are, according to our implementation, the Promise instance returned after the last “THEN” call is returned. As for how to call, in fact, we have implemented, it is a bit like recursion, after handling the logic, let the function constantly call itself.

To solve the first problem, we use the _handleSuccessCallback function. The _handleSuccessCallback takes a single argument, resolve, in the Promise constructor, but remember that resolve is the resolve of the Promise instance returned after the then call.

The pass-through call returns a new function that also takes a parameter val, the value determined in the Promise instance that calls the then method and then instance. The new function is called when the THEN instance state is DESCENDANT, or when the THEN instance is converted from pending to FULFILL, the resolve method in the constructor is called.

When a new function is called, successCallback first passes the value of the then instance resolution as a parameter to execute, logging the result as result. If result is a Promise instance, add a THEN and pass the value of the result resolution to the resolve execution of the return instance. If it’s not a Promise instance, it’s better to pass Result as an argument to the resolve call that returns the instance.

At this point, both problems are solved. Regardless of whether the function in the incoming THEN is asynchronous or synchronous, we can pass the value returned by its call or the resolved value to the subsequent THEN execution.

Write a small example to test it:

const p = new Promise(function(res) {
  setTimeout(() => {
    res(123);
  }, 1000);
});

p.then(function(val) {
  console.log(val);
  return new Promise(function(res) {
    setTimeout(() => {
      res(456);
    }, 1000);
  }).then(val => {
    console.log(val);
    return 789;
  })
}).then(val => console.log(val));Copy the code

When the browser runs this example, it prints 123 after 1 second, 456 after 1 second, and then 789. There is no problem with the logical whole.

summary

At this point, the self-implemented Promise is complete, although this is the crudest implementation, missing a lot of functionality, like Reject, promise.resolve, promise.reject, etc. However, by looking at the examples above, you can easily implement other features. Due to space constraints, the approximate function is no longer implemented.

In fact, understanding the implementation is secondary, mainly the implementation process used in the skills is very worth learning. Skillful asynchronous programming is a must for every front-end. Thank you to see the adult here ~ hope this article is helpful to you. Thank you very much!

The resources

Node.js Practice Tutorial – Promise implementation

30 minutes to make sure you understand the Promise principle