preface

Promise is a built-in object added to ES6 as a solution to avoid callback hell.

From always nesting callback functions, to using Promise to chain asynchronous callbacks. How exactly does Promise get implemented to “flatten” the callback function?

The next step is to implement a simple Promise. It’s time to start…

step

Let’s start with a simple example of using promises:

var p = new Promise(function a (resolve) {
  console.log(1);
  setTimeout(function () {
    resolve(2);
  }, 1000);
})

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

Code executes, it will execute function A first, printing out 1. Resolve is executed after the timer is 1 second, followed by function B.

The more detailed steps look like this:

  1. New Promise, executes the Promise constructor;
  2. In the constructor, execute function A;
  3. Execute the then function;
  4. After 1 second, the resolve function is executed;
  5. Execute b function.

One idea is to hold function B in a property when the THEN function executes, and then execute it when resolve executes.

Began to encapsulate

Here we define a MyPromise with a then function and a callback attribute to hold the “B function” above.

function MyPromise (fn) {
  var _this = this;
  
  // Save the callback function passed by then
  this.callback = undefined;
  
  function resolve (val) {
    _this.callback && _this.callback(val);
  }
  
  fn(resolve);
}

MyPromise.prototype.then = function (cb) {
  this.callback = cb;
};
Copy the code

Test use:

var p = new MyPromise(function (resolve) {
  console.log(1);
  setTimeout(function () {
    resolve(2);
  }, 1000);
});

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

When the code executes, it prints 1 immediately and 2 1 second later. Hair problems.

Multiple resolve calls are processed

Having implemented a simple Promise above, there are, of course, many more scenarios to consider.

For example, there is a case where multiple resolve functions are called:

var p = new MyPromise(function (resolve) {
  console.log(1);
  setTimeout(function () {
    resolve(2);
    resolve(3);
    resolve(4);
  }, 1000);
});
Copy the code

The native Promise calls the first resolve and invalidates all subsequent resolve, meaning that all subsequent resolve is useless code.

To do this, add an isResolved property to MyPromise to record whether the resolve function has been called. If so, use it to identify it. A call to resolve is then used to determine the return.

function MyPromise (fn) {
  var _this = this;

  this.callback = undefined;
  this.isResolved = false;
  
  function resolve (val) {
    if (_this.isResolved) return;
    _this.isResolved = true;
    _this.callback && _this.callback(val);
  }
  
  fn(resolve);
}
Copy the code

Multiple THEN processing

Moving on, in addition to calling multiple resolve functions, we can also call multiple THEN functions.

var p = new MyPromise(function (resolve) {
  console.log(1);
  setTimeout(function () {
    resolve(2);
  }, 1000);
});

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

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

Unlike resolve, each callback to THEN is executed after one second, meaning that the THEN function is valid. Code execution, first print 1. After 1 second, print two 2’s.

So MyPromise’s callback property needs to be an array that holds each then callback.

function MyPromise (fn) {
  var _this = this;

  this.callback = [];
  this.isResolved = false;
  
  function resolve (val) {
    if (_this.isResolved) return;
    _this.isResolved = true;
    
    if (_this.callback.length > 0) {
      _this.callback.forEach(function (func) {
        func && func(val);
      });
    }
  }
  
  fn(resolve);
}

MyPromise.prototype.then = function (cb) {
  this.callback.push(cb);
};
Copy the code

On multiple calls to THEN, MyPromise holds multiple callback functions through the attribute callback. After resolve, the callback is iterated through, executing its saved callback functions one by one.

Support for THEN chain calls

Promise’s advantage over callback hell is that it can make chaining calls to THEN, flattening the callback function.

For example, if I have an asynchronous operation that needs to be executed after another asynchronous operation, I just need to continue calling THEN to callback the next step.

var p = new MyPromise(function (resolve) {
  console.log(1);
  setTimeout(function () {
    resolve(2);
  }, 1000);
});

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

If I want to be able to call then chain, I can return this after executing then function. But isn’t that the same code as we just did?

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

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

Instead of executing the first THEN and then the second, resolve is called at the same time. So returning this is not going to work.

Let’s think about what else we can return with a then function. The answer is new a new MyPromise and return. We need to rewrite the implementation of a then function.

MyPromise.prototype.then = function (cb) {
  var _this = this;

  return new MyPromise(function (resolve) {
    _this.callback.push({
      cb: cb,
      resolve: resolve
    });
  });
};
Copy the code

Callback is rewritten to hold the then callback and the new MyPromise resolve function. The saved resolve function is not executed because we know that once it is executed, it triggers the execution of the callback passed in to THEN.

Also, resolve in the MyPromise constructor needs to be adjusted:

function MyPromise (fn) {
  var _this = this;

  this.callback = [];
  this.isResolved = false;
  
  function resolve (val) {
    if (_this.isResolved) return;
    _this.isResolved = true;

    if (_this.callback.length > 0) {
       _this.callback.forEach(function (item) {
       var res;
       var cb = item.cb;
       var resolve = item.resolve;
       
       cb && (res = cb(val));
       resolve && resolve(res);
     });
    }
  }
  
  fn(resolve);
}
Copy the code

When the first then callback is executed, the value returned by it is passed as an argument to the saved resolve.

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

In this way, we can make chained then calls. Don’t worry, we’re just implementing synchronous chain calls to THEN, and we’re going to end up with asynchronous chain calls.

We need something like this:

p.then(function (val) {
  console.log(val);
  
  return new MyPromise(function (resolve) {
    setTimeout(function () {
      resolve(val + 1);
    }, 1000);  
  });
}).then(function(val) {
  console.log(val);
});
Copy the code

Print 1,1 second, then print 2 in the first then, and one more second, print 3 in the second then.

Therefore, we need to take out the value returned by the original saved CB to judge. If the value is a MyPromise object, its THEN is called, otherwise it is called as before.

function MyPromise (fn) {
  var _this = this;

  this.callback = [];
  this.isResolved = false;
  
  function resolve (val) {
    if (_this.isResolved) return;
    _this.isResolved = true;

    if (_this.callback.length > 0) {
      _this.callback.forEach(function (item) {
        var res;
        var cb = item.cb;
        var resolve = item.resolve;
        
        cb && (res = cb(val));
        if (typeof res === 'object' && res.then) {
          res.then(resolve);
        } else{ resolve && resolve(res); }}); } } fn(resolve); }Copy the code

The last

In the MyPromise we implemented, there were two properties, isResolved and Callback. IsResolved is a flag to prevent multiple calls to Resolve. Callback is an array that holds the callback function.

MyPromise also has a then function that handles post-async callbacks and can be called asynchronously by chain.

This fulfils a simple Promise. The full code is here.