Characteristics of promise

The current state of a promise can only be one of pending, depressing and Rejected. State changes can only be pending to depressing or pending to Rejected. State change is irreversible. Support for chain calls. (1) Prototype method

Promise.prototype.then = function() {}
Promise.prototype.catch = function() {}
Copy the code

(2) Static method

Promise.resolve = function() {}
Promise.reject = function() {}
Promise.all = function() {}
Promise.race = function() {} / /...Copy the code

Promise’s pros and cons: State immutable chain calls solve callback hell, making code cleaner and easier to maintain. Disadvantages: Can’t abort in execution and can’t see what state asynchrony is in pending.

Promise source code implementation

So first let’s see how do we use Promise

new Promise(function (resolve, reject) {
    setTimeout(() => {
        // resolve("Asynchronous success pull");
        reject("Asynchronous failed");
    }, 1000);
}).then(
function(data) { console.log(data); / *thenThis can be synchronous code or asynchronous promise */ //return new Promise(function (resolve, reject){
    //     setTimeout(() => {
    //         resolve("Asynchrony in the first THEN");
    //     }, 1000);
    // });

    return "Return value of chain call first THEN";
},
function (reason) {
    console.log("The first then" + reason);
    return "Failure after first Then Reject reason"})Copy the code

Implement a simple Promise constructor

functionPromise(executor) { var _this = this; this.data = undefined; // Data this.status ="pending"; // State this.onresolvedCallback = [] This. OnRejectedCallback = [] // The set of callback functions used when a Promise is rejected. Var resolve = because more than one callback may be added to a Promise before it endsfunction (data){
        if (_this.status === "pending"){
            _this.status = "resolved";
            _this.data = data;
            for(var i = 0; i < _this.onResolvedCallback.length; i++) {
                _this.onResolvedCallback[i](data)
            }
        }
    }

    var reject = function (errReason) {
        if (_this.status === "pending"){
            _this.status = "rejected";
            _this.data = errReason;
            for(var i = 0; i < _this.onRejectedCallback.length; i++) { _this.onRejectedCallback[i](errReason) } } } try{ executor(resolve, reject); } catch(e){ reject(e); }}Copy the code

As you can see from the code above, executors should generally be asynchronous, waiting for their execution to succeed or fail, and then executing their resolve or reject callbacks. Then execute the then registered callback in resolve or Reject. So the then function should be a callback from the registered user to onResolvedCallback or onRejectedCallback.

Then the implementation of the

Before implementing the THEN function, let’s clarify what the THEN function does. Callback to _this.onResolvedCallback or _this.onRejectedCallback. This promise should be a new promise. Executor can be a synchronous function or an asynchronous function, so _this.status can be pending when executing then. _this.status can be resolve/reject (executor is synchronous), while status pending is a registration process that stores the callback and waits for status to become resolve or Reject. If status is resolve/reject, the swap is performed directly. The above explanation suggests understanding the above while looking at the following code

//onResolved onRejected (' stoppage onRejected ')function(onResolved, onRejected){ var _this = this var promise2; // According to the standard, ifthenThe argument is notfunction, we need to ignore it and handle onResolved = typeof onResolved == here'function' ? onResolved : function(value) {}
    onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {} // If the executor above is an asynchronous, executethen"Status must be pendingif (this.status === "pending"){
        //生成一个新的promise
        promise2 = new Promise(function(resolve, reject) {/ / will be packed the caller's callback after registered into the promise of callback queue. _this onResolvedCallback. Push (function{var x = onResolved(_this.data) {if (x instanceof Promise) {
                        //thenIf the callback is an asynchronous promise, wait until the async execution is complete before entering the promise2 callbackthenThe registered callback x. tan (resolve, reject) in. }else{// If it is synchronous, go directly to promise2thenResolve (x); } } catch (e) { reject(e) } }); _this.onRejectedCallback.push(function (reason) {
                try {
                    var x = onRejected(_this.data)
                    if (x instanceof Promise) {
                      x.then(resolve, reject);
                    }
                    else{
                        reject(x);
                    }
                } catch (e) {
                    reject(e)
                }
            });
        })
        returnpromise2; } // Execute if executor is synchronousthen"Status resolved or Rejectedif (_this.status === 'resolved'{// If the state of promise1 is resolved and is resolved, we call onResolved // in a try/catch block because of the possibility of throwingreturn promise2 = new Promise(function(resolve, reject) {
          try {
            var x = onResolved(_this.data)
            if(x instanceof Promise) {// If onResolved returns a Promise, X. resolve(resolve, reject)} resolve(x)} catch (e) {reject(e) {// If there is an error, Capture errors as the result of promise2}})} //ifBlock logic is almost the same except that the onRejected function is calledif (_this.status === 'rejected') {
        return promise2 = new Promise(function(resolve, reject) {
          try {
            var x = onRejected(_this.data)
            if (x instanceof Promise) {
              x.then(resolve, reject)
            }
          } catch (e) {
            reject(e)
          }
        })
    }
}  
Copy the code

After I look at the code and move on to explain the processing in promisE2, what I need to think about now is how to execute in promisE2 and how to deliver the correct data to the next THEN. If x (onResolved or onRejected) is a normal value, then call resolve or Reject. If x is a promise object, We need to wait for the state of the promise to be reosolve or reject, that is, until the promise completes the asynchronous task (we need to use the promise, which is usually asynchronous), so we call x. Chen (resove, Reject), The resolve/ Reject of promise2 is directly used as a callback. This is the process of waiting for the callback to be delivered to the THEN of the promise2 after the promise x is executed.

The realization of the catch

Promise.prototype.catch = function (onRejected) {
    return this.then(null, onRejected)
}
Copy the code

At this point, even if the whole principle of promise is mostly complete, it’s not that hard to understand, right?

Make sure the processing of X interacts with different promises

According to the Promise A+ specification, the whole process of processing X in promisE2 and passing the processing value to the callback of promise2. Then does not take into account “the case of an object that does not comply with the Promise specification and has A then method”, The Promise A+ specification hopes to deliver X to promise2. Then in the safest way possible, even if X is an object that does not follow the Promise specification but carries the THEN. So you need to do a little bit more with x, and then you can move the data to the next step

/ We will treat the onResolved/onRejected return value X as a possible Promise object and call the THEN method on X in the safest way. Then different promises can interact with each other. The standard, to be on the safe side, even if X returns an object/recursive solution with then attributes that don’t follow the Promise standard, as long as X has then methods, it will peel off layers like an onion until X is a non-Promise-like asynchronous object, a non-Thennable object.

function resolvePromise(promise2, x, resolve, reject) {
    var then
    var thenCalledOrThrow = false
  
    if(promise2 === x) {// Corresponds to section 2.3.1 of the standardreturn reject(new TypeError('Chaining cycle detected for promise! '))}if(x instanceof Promise) {// If the state of x has not been determined, then it is possible for a thenable to determine the final state and value // so we need to do something here. It should not be assumed that it will be resolved by a "normal" valueif (x.status === 'pending') {
        x.then(function(value) {
          resolvePromise(promise2, value, resolve, reject)
        }, reject)
      } else{// But if the Promise state is already established, it must have a "normal" value instead of a thenable, so it takes its state x.tran (resolve, reject)}return
    }
  
    if((x ! == null) && ((typeof x ==='object') || (typeof x === 'function'))) {// 2.3.3 try {// 2.3.3.1 Because x.chen may be a getter, in which case multiple reads may have side effects // that is, to determine its type and to call it, that is, two readsthen = x.then 
        if (typeof then= = ='function') {// 2.3.3.3 then.call(x,functionRs (y) {/ / 2.3.3.3.1if (thenCalledOrThrow) return// 2.3.3.3.3 That is, the result of the execution of the three shall prevailthenCalledOrThrow = true
            returnResolvePromise (promise2, y, resolve, reject)functionRj (r) {/ / 2.3.3.3.2if (thenCalledOrThrow) return// 2.3.3.3.3 That is, the result of the execution of the three shall prevailthenCalledOrThrow = true
            return reject(r)
          })
        } else{/ / 2.3.3.4 resolve (x)}} the catch (e) {/ / 2.3.3.2if (thenCalledOrThrow) return// 2.3.3.3.3 That is, the result of the execution of the three shall prevailthenCalledOrThrow = true
        return reject(e)
      }
    } else{// resolve(x)}}Copy the code

Let’s change the promise2 process, change all three.

promise2 = new Promise(function(resolve, reject) {/ / will be packed the caller's callback after registered into the promise of callback queue. _this onResolvedCallback. Push (function(value){// Try {var x = onResolved(value); // Resolve that the onResolved return value x defined by the caller is a non-canonical Promise object withthenResolvePromise (promise2, x, resolve, reject); } catch (e) { reject(e) } }); _this.onRejectedCallback.push(function (reason) {
                try {
                    var x = onRejected(reason)
                    resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    reject(e)
                }
            });
        })
        return promise2;
Copy the code

Test the

// Test objects that do not comply with promises, and withthenMethod var notPromise =function () {
    //
}

notPromise.prototype.then = function (onResolved, onRejected) {
    setTimeout(function () {
        onResolved("Objects that don't abide by the Promise specification."// test new Promise()function (resolve, rejected) {
    setTimeout(function () {
        resolve("Asynchrony begins.");
    },1000)
}).then(function(data) { console.log(data); // The following returns can be either a promise or a normal value, or they can be objects that are not allowed to find the Promise specification but containthenMethods (both supported in resolve) // Plain values and promises are not tested. // Test an object that follows a promise withthenmethodsreturn new notPromise();
}).then(function(data) {/ / inthenIt keeps calling the previous value (new notPromise())thenMethod, know to make sure there is nothenWhen it's ready to call, pass it on to the next onethenconsole.log(data); // The data here is not (new notPromise()) but itsthenMethod returns. })Copy the code

Value penetration problem

new Promise(resolve=>resolve(8))
  .then()
  .catch()
  .then(function(value) {
    alert(value)
  })
Copy the code

What we need to achieve is that this code up here behaves the same as this code down here

new Promise(resolve=>resolve(8))
  .then(function(value){
    return value
  })
  .catch(function(reason){
    throw reason
  })
  .then(function(value) {
    alert(value)
  })
Copy the code

If no callback is returned, the default callback is used, so change on the default callback and let it pass the value

onResolved = typeof onResolved === 'function' ? onResolved : function(value) {return value}
onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {throw reason}
Copy the code

Promise termination questions (Interview questions)

In some scenarios, we might have a long Promise chain-call, and an error in one step makes it unnecessary to run all the code after the chain-call, like the following (then/catch omitted) : give the result first

Promise.cancel = Promise.stop = function() {
    return new Promise(functionAlert (1) new Promise() alert(1) new Promise()function(resolve, reject) {
    resolve(42)
  })
    .then(function(value) {
        var isErr = true; // Try to change totrueorfalseThe alert (1); Whether or not to performif (isErr){
            // "Big ERROR!!!"
            returnPromise. Stop ()}}) / / value through the catch (). Then (). Then (). The catch (). Then (function () {
        alert(1);
    })
Copy the code

Return new Promise(function(){}); Then (resolve,reject) delivers the THEN to promise2. But new Promise(function(){} has no resolve or reject at all, so its state is always pending, so it will never execute resolve/reject in x. Chen (resolve,reject). Then the state doesn’t transfer, and the chain breaks.

There is no catch on the promise chain

If there is no error handler, give a default error handler

function reject(reason) {
        setTimeout(function() {
          if (_this.status === 'pending') {
            _this.status = 'rejected'
            _this.data = reason
            if(_this. OnRejectedCallback. Length = = = 0) {console. Error (reason) / / the default error handling}for (var i = 0; i < _this.rejectedFn.length; i++) {
              _this.rejectedFn[i](reason)
            }
          }
        })
    }
Copy the code

Implementation of the Promise static method

List a few more commonly used, very easy to understand, look at the code basic can understand, especially promise.all Promise. Race implementation oh, often test the principle of the interview.

Promise.all = function(promises) {
    return new Promise(function(resolve, reject) {
      var resolvedCounter = 0
      var promiseNum = promises.length
      var resolvedValues = new Array(promiseNum)
      for (var i = 0; i < promiseNum; i++) {
        (function(i) {
          Promise.resolve(promises[i]).then(function(value) {
            resolvedCounter++
            resolvedValues[i] = value
            if (resolvedCounter == promiseNum) {
              return resolve(resolvedValues)
            }
          }, function(reason) {
            return reject(reason)
          })
        })(i)
      }
    })
  }

  Promise.race = function(promises) {
    return new Promise(function(resolve, reject) {
      for (var i = 0; i < promises.length; i++) {
        Promise.resolve(promises[i]).then(function(value) {
          return resolve(value)
        }, function(reason) {
          return reject(reason)
        })
      }
    })
  }

  Promise.resolve = function(value) {
    var promise = new Promise(function(resolve, reject) {
      resolvePromise(promise, value, resolve, reject)
    })
    return promise
  }

  Promise.reject = function(reason) {
    return new Promise(function(resolve, reject) {
      reject(reason)
    })
  }
Copy the code

Then give a full version of the code

try {
    module.exports = Promise
  } catch (e) {}
  
  function Promise(executor) {
    var self = this
  
    self.status = 'pending'
    self.onResolvedCallback = []
    self.onRejectedCallback = []
  
    function resolve(value) {
      if (value instanceof Promise) {
        return value.then(resolve, reject)
      }
      setTimeout(function() {// Execute all callbacks asynchronouslyif (self.status === 'pending') {
          self.status = 'resolved'
          self.data = value
          for (var i = 0; i < self.onResolvedCallback.length; i++) {
            self.onResolvedCallback[i](value)
          }
        }
      })
    }
  
    function reject(reason) {
      setTimeout(function() {// Execute all callbacks asynchronouslyif (self.status === 'pending') {
          self.status = 'rejected'
          self.data = reason
          for (var i = 0; i < self.onRejectedCallback.length; i++) {
            self.onRejectedCallback[i](reason)
          }
        }
      })
    }
  
    try {
      executor(resolve, reject)
    } catch (reason) {
      reject(reason)
    }
  }
  
  function resolvePromise(promise2, x, resolve, reject) {
    var then
    var thenCalledOrThrow = false
  
    if (promise2 === x) {
      return reject(new TypeError('Chaining cycle detected for promise! '))}if (x instanceof Promise) {
      if (x.status === 'pending') { //because x could resolved by a Promise Object
        x.then(function(v) {
          resolvePromise(promise2, v, resolve, reject)
        }, reject)
      } else { //but if it is resolved, it will never resolved by a Promise Object but a static value;
        x.then(resolve, reject)
      }
      return
    }
  
    if((x ! == null) && ((typeof x ==='object') || (typeof x === 'function'))) {
      try {
        then = x.then //because x.then could be a getter
        if (typeof then= = ='function') {
          then.call(x, function rs(y) {
            if (thenCalledOrThrow) return
            thenCalledOrThrow = true
            return resolvePromise(promise2, y, resolve, reject)
          }, function rj(r) {
            if (thenCalledOrThrow) return
            thenCalledOrThrow = true
            return reject(r)
          })
        } else {
          resolve(x)
        }
      } catch (e) {
        if (thenCalledOrThrow) return
        thenCalledOrThrow = true
        return reject(e)
      }
    } else {
      resolve(x)
    }
  }
  
  Promise.prototype.then = function(onResolved, onRejected) {
    var self = this
    var promise2
    onResolved = typeof onResolved === 'function' ? onResolved : function(v) {
      return v
    }
    onRejected = typeof onRejected === 'function' ? onRejected : function(r) {
      throw r
    }
  
    if (self.status === 'resolved') {
      return promise2 = new Promise(function(resolve, reject) {
        setTimeout(function() {// implement onResolved try {var x = onResolved(self.data) resolvePromise(promise2, x, resolve, reject) } catch (reason) { reject(reason) } }) }) }if (self.status === 'rejected') {
      return promise2 = new Promise(function(resolve, reject) {
        setTimeout(function() {var x = onRejected(self.data) resolvePromise(promise1, x, resolve, reject) } catch (reason) { reject(reason) } }) }) }if (self.status === 'pending'{// There is no asynchronous execution because these functions must be called resolve or reject, which are defined in the constructor for asynchronous executionreturn promise2 = new Promise(function(resolve, reject) {
        self.onResolvedCallback.push(function(value) {
          try {
            var x = onResolved(value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (r) {
            reject(r)
          }
        })
  
        self.onRejectedCallback.push(function(reason) {
            try {
              var x = onRejected(reason)
              resolvePromise(promise2, x, resolve, reject)
            } catch (r) {
              reject(r)
            }
          })
      })
    }
  }
  
  Promise.prototype.catch = function(onRejected) {
    return this.then(null, onRejected)
  }
  
  Promise.deferred = Promise.defer = function() {
    var dfd = {}
    dfd.promise = new Promise(function(resolve, reject) {
      dfd.resolve = resolve
      dfd.reject = reject
    })
    return dfd
  }
Copy the code

Other static method code reference

Github.com/ab164287643…

In this paper, the reference

Github.com/xieranmaya/…