Using Promise/A+ as A standard, write A Promise library that can pass standard tests.

What is a Promise

  • It is mainly used for asynchronous computing
  • Asynchronous operations can be queued, executed in the desired order, and returned as expected. Avoid pullback territory
  • Promises can be passed and manipulated between objects to help us deal with queues

The structure of the Promise class

  1. The Promise object’s initial state is Pending. When it is resolved or Reject, the state becomes Fulfilled or Rejected
  2. Resolve Receives successful data, reject receives failed or incorrect data
  3. The Promise object must have a THEN method and only accept two functionable parameters onFulfilled and onRejected
const REJECTED = 'rejected'; const RESOLVED = 'resolved'; const PENDING = 'pending'; class MPromise{ constructor(resolver){ if(resolver && typeof resolver ! == 'function'){ throw new Error('MPromise resolver is not function'); } this.state = PENDING; // The current state of the promise object this.data = undefined; // Data for the current Promise object (success or failure) this.callbackQueue = []; If (resolver){executeresolver. call(this, resolver); } } then(){ //todo } }Copy the code

So, a Promise constructor and an instance method then are the core of the Promise, and everything else is syntactic sugar or extension of the Promise

executeResolver

Constructor initialization, using new Promise(function(resolve, reject){… }) when a Promise is instantiated, the state of the Promise is changed by executing resolve() or reject(). The two parameters of the resolver are the successful and failed action functions, respectively.

function executeResolver(resolver){ let called = false; // let _this = this; function onError(reason){ if(! called){ return; } called = true; executeCallback.call(_this,'reject', reason); } function onSuccess(value){ if(! called){ return; } called = true; executeCallback.call(_this, 'resolve', value); } try{ resolver(onSuccess, onError); }catch(e){ onError(e); }}Copy the code

This abstracts the method that executes resolver above, and internally wraps the resovle and Reject arguments into successful and failed callbacks.

executeCallback

Resolve () or Reject () changes the status of the current instance to Rejected or Resolved, and then executes the successful and failed callback registered in the current instance then()

function executeCallback(type, x){ const isResolve = type === 'resolve' ? true : false; let thenable; if(isResolve && typeof x === 'object' && typeof x.then === 'function'){ try { thenable = getThen(x); } catch (e) { return executeCallback.call(this, 'reject', e); } } if(isResolve && thenable){ executeResolver.call(this, thenable); This} else {this.state = isResolve? RESOLVED : REJECTED; this.data = x; this.callbackQueue.forEach(v => v[type](x)); } return this; }Copy the code
function getThen(obj){
    const then = obj && obj.then;
    if(obj && typeof obj === 'object' && typeof then === 'function'){
        return applyThen(){
            then.apply(obj, arguments)
        }
    }
}
Copy the code

then

The standard stipulates that:

  1. The then method must return A new Promise instance (standard in ES6, not specified in Promise/A+)
  2. In order to ensure the order of callback execution in THEN, onFulfilled or onRejected must be called asynchronously
class MPromise{ ... Then (onResolved, onRejected){// This. State === RESOLVED && onResolved! == 'function' || this.state === REJECTED && onRejected ! == 'function'){ return this; } let promise = new MPromise(); if(this.state ! == PENDING){ var callback = this.state === RESOLVED ? onResolved : onRejected; / / note: incoming promise, / / the asynchronous invocation executeCallbackAsync. Call (promise, callback, enclosing the data). } else { this.callbackQueue.push(new CallbackItem(promise, onResolved, onRejected)) } return promise; // You must return promise to chain call}}Copy the code

executeCallbackAsync

The logic for calling callback asynchronously is abstracted into a method called executeCallbackAsync, which executes the callback safely:

  1. If something goes wrong, the Reject (Reason) method is automatically called and the state is changed to Rejected, passing the error data to the onRejected callback registered in the current instance’s THEN method
  2. If successful, the resolve(value) method is automatically called and the state is changed to Resolved, passing data to the onResolved callback registered in the current instance then method
function executeCallbackAsync(callback, value){
    let _this = this;
    setTimeout(() => {
        let res;
        try{
            res = callback(value);
        }catch(e){
            return executeCallback.call(_this, 'reject', e);
        }
        if(res !== _this){
            return executeCallback.call(_this, 'resolve', res);
        } else {
            return executeCallback.call(_this, 'reject', new TypeError('Cannot resolve promise with itself'));
        }
    }, 4);
}
Copy the code

SetTimeout can be used to execute the callback asynchronously, but it is not really an asynchronous thread. Instead, the browser’s Event Loop mechanism is used to trigger the callback execution, and the browser’s Event Loop time is 4ms. Therefore, the connection call setTimeout will have a time interval of 4ms, while the Event Loop in Nodejs will have a time interval of 1ms, so there will be a certain delay. If the promise chain is longer, the delay will be more obvious. You can introduce the immediate module on NPM to perform callbacks asynchronously and without delay.

CallbackItem

The processing of callbacks in THEN above uses a callback object to manage registered callbacks, adding them sequentially to the callbackQueue queue, and calling them sequentially as they are called.

class CallbackItem { constructor(promise, onResolved, onRejected) { this.promise = promise; this.onResolved = typeof onResolved === 'function' ? onResolved : function (v) { return v; }; this.onRejected = typeof onRejected === 'function' ? onRejected : function (v) { throw v; }; } resolve(value) { executeCallbackAsync.call(this.promise, this.onResolved, value); } reject(value) { executeCallbackAsync.call(this.promise, this.onRejected, value); }}Copy the code

example

The above references provide an in-depth understanding of the articles in Promise to implement MPromise

function fn() { let promise1 = new MPromise((resolve, reject) => { resolve(1); }); new MPromise((resolve, reject) => { resolve(promise1); Then (res => {console.log(res); // System execute promise1. Then}).then(res => {console.log(res); return 222; }).catch(err => { console.log(err); }); } fn(); / / 1Copy the code

Refer to the article

Understanding Promise in depth (Middle)

Git address for MPromise