The overall structure meets A+ specification

It is not necessary to implement all of the Promise functions in the PromiseA+ specification, just the following structures:

function Promise (exectuer) {
    this.promiseState = 'pending'
    this.promiseResult = null
    this.callback = []
    
    function resolve (data) {
        ......
    }
    
    function rejected (data) {
        ......
    }
    
    try {
        exectuer(resolve, reject)
    } catch (error) {
        reject(error)
    }
}

Promise.prototype.then = function (onResolved, onRejected) {
    ......
}

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

Once the overall structure is in place, use scenarios for actual promises can be implemented step by step.

Satisfy the concrete implementation of A+ specification

// 声明构造函数Promise
function Promise(executer) {
  /**
   1. 添加属性Promise相关属性,promiseState,promiseResult,callback
   2. promiseState用来保存Promise的状态:'pending','fulfilled','rejected'
   3. promiseResult用来保存Promise最终的结果
   4. callback是一个数组,用来保存resolve或reject方法在Promise中被异步调用时,then方法中的回调函数
   5. 将这三个值放在this中,可以方便Promise实例在调用then方法时可以访问到
  */
  this.promiseState = 'pending'
  this.promiseResult = null
  this.callback = []
  
  // 保存this值,方便resolve和reject方法中访问当前对象
  var self = this

  // 创建resolve函数,作为executer的参数,下reject同
  function resolve(data) {
  
    /**
     promise的状态只能从pending变成fulfilled或者rejected,只能改变一次
     因此调用resolve和reject的时候要对promise的状态进行判断,防止下列情况而导致多次执行:
     var promise = new Promise((resolve, reject) => {
         resolve('success')
         reject('error')
         // 这种情况下若是不加判断,promise的状态会从pending变成fulfilled再变成rejected
     })
    */
    if (self.promiseState !== 'pending') return
    
    /**
     修改对象的状态(promiseState)和结果值(promiseResult)
     因为这里是resolve函数,因此状态只能变成fulfilled,结果值跟resolve的参数相等
     */
    self.promiseState = 'fulfilled'
    self.promiseResult = data
    
    /**
     Promise是一个微任务,在node环境中使用process.nextTick()来实现模拟微任务
     在浏览器环境中使用queueMicrotask()来实现模拟微任务
     当然使用setTimeout来模拟同样也能满足A+规范,但setTimeout属于宏任务,感觉不太贴切
    */
    queueMicrotask(() => {
      /**
       这里用来统一执行then中的回调函数,具体针对以下使用场景:
       var promise = new Promise((resolve, reject) => {
           setTimeout(() => {
               resolve()
           })
       })
       promise.then(value => {
           console.log(value)
       }).then(value => {
           console.log(value)
       }).then(value => {
           console.log(value)
       })
       按照Promise的规范来说,只有当resolve或者reject执行之后改变了promise的状态,
       才能执行promise.then()中的回调,但是在上面的这种情况下,
       resolve方法被放到了setTimeout中执行,变成了异步执行,
       根据JS的执行顺序,promise.then()中的回调函数将会直接执行,
       因此为了满足规范的要求,就需要在then执行时判断当前状态是否是pending,
       如果是pending的话,就要将then中的回调函数都保存在callback中,
       然后当resolve或者reject被调用时再在这里用forEach遍历并执行
      */
      self.callback.forEach(item => {
        item.onResolved()
      })
    })
  }

  // reject的原理跟resolve一样,不同之处就是将promiseState的值变成了rejected
  function reject(data) {
    if (self.promiseState !== 'pending') return
    self.promiseState = 'rejected'
    self.promiseResult = data
    queueMicrotask(() => {
      self.callback.forEach(item => {
        item.onRejected(data)
      })
    })
  }

  /**
   这里用try...catch来对throw抛出的异常进行监听,为了满足以下使用:
   var promise = new Promise((resolve, reject) => {
       throw 'error'
   })
   在上述情况下,不执行resolve和reject方法,也要能改变promise的状态为rejected,
   因此就需要在executer执行时,用try...catch进行异常捕获,当捕获到错误时执行reject方法
  */
  try {
  
    /**
     同步调用,执行器函数executer,这里是Promise同步执行的部分,
     总的来说Promise的异步体现在then方法中,executer的执行其实是同步的
     */
    executer(resolve, reject)
    
  } catch (error) {
  
    // 有异常时,改变promise状态为rejected
    reject(error)
  }
}

// 将then方法添加在Promise的原型中,满足实例promise的调用,如promise.then()
Promise.prototype.then = function (onResolved, onRejected) {

  // 将当前对象this保存在self中,当前this指向即为Promise构造函数的实例对象
  var self = this
  
  /**
   接下去是对参数onResolved和onRejected的判断和处理,因为then方法中的两个参数都需要是函数
   当这两个参数都为函数时,不做处理,如果不是函数,那么就将其重新封装成函数,方便后续执行
  */
  if (typeof onResolved !== 'function') {
    onResolved = value => {
    
      /**
       这里的value值是为了针对以下情况:
       var promise = new Promise((resolve, reject) => {
           resolve()
       }).then(res => {
           return 'success'
       }).then(4).then(res => {
           console.log(res) // 'success'
       })
       这里就相当于将上述.then(4)变成了.then((value) => { return value })
       这样就跟其他的then方法写法没什么两样了,并且value的值是上一个then中返回的'success'
       如此就能让下一个then中的参数是这个'success'了
      */
      return value
    }
  }
  
  if (typeof onRejected !== 'function') {
    onRejected = reason => {
    
    /**
     这里onRejected方法与onResloved不同的地方在于onRejected要throw一个reason而不是return
     首先这里若是使用return,那么Promise将无法进行异常穿透,导致下列场景无法实现:
     let promise = new Promise((resolve,reject) => {
       reject('err')
     }).then(res => {
       console.log(1, res)
     }).then(res => {
       console.log(2, res) // 如果使用return,那么这里将会执行,异常将无法穿透
     }).catch(reason => {
       console.log(3, reason)
       // 如果使用throw,那么异常将会逐层抛出,直到进入catch,或者then方法的onRejected为函数
     })
    */
      throw reason
    }
  }


  // 创建一个新的promise实例,用作调用then之后的返回值,这样保证then可以链式调用
  let promise2 = new Promise((resolve, reject) => {
  
    // 判断调用了then方法的promise对象的状态,不同状态下对应不同的操作
    if (self.promiseState === 'fulfilled') {
      
      // 用queueMicrotask模拟微任务,具体上面resolve函数中有讲到
      queueMicrotask(() => {
      
        /**
         用try...catch进行异常捕获,因为then中的回调有可能会抛出异常,比如:
         new Promise((resolve, reject) => {
             resolve()
         }).then(value => {
             throw 'Error'
         })
         在遇到上述情况时,就要对then中回调函数的执行进行监听,
         抛出错误的时候执行reject方法将其状态改成rejected
        */
        try {
        
          /**
           首先执行then中的回调函数,当然这里的onResolved一定是个函数,
           因为在上面已经进行了对onResolved和onRejected的判断
           同时onResolved的参数是上一个resolve的参数值,
           而执行resolve函数时会将参数赋值给promiseResult,
           因此这里onResolved的参数就是self.promiseResult
           */
          var result = onResolved(self.promiseResult)
          
          /**
           为了then能够链式调用,因此要返回一个新的promise,
           但是新的promise也要给它一个状态,fulfilled或者rejected,
           不然的话下一个then方法中的函数将无法执行
           这一步的简单实现其实是这样的:
           if (result instancef Promise) {
               result.then(value => {
                   resolve(value)
               }, reason => {
                   reject(reason)
               })
           } else {
               resolve(result)
           }
           简单来说就是看result是否是promise实例,如果是,那么通过result.then
           来决定我们当前返回的promise的状态,并将它的参数value传入,
           解决了下面这种在then中返回一个promise实例的情况:
           new Promise((resolve, reject) => {
               resolve()
           }).then(res => {
               return new Promise((resolve, reject) => {
                  resolve('success')
               })
           }).then(res => {
               console.log(res) // 'success'
           })
           但是这个result还有其他的可能性,因此这里就统一放在resolvePromise函数中判断解决
           */
          resolvePromise(promise2, result, resolve, reject)
          
        } catch (error) {
          reject(error)
        }
      })
    }
   
    if (self.promiseState === 'rejected') {
      process.nextTick(() => {
        try {
          var result = onRejected(self.promiseResult)
          resolvePromise(promise2, result, resolve, reject)
        } catch (error) {
          reject(error)
        }
      })
    }
   
    /**
     判断是否为pending状态,这种状态下执行then方法往往是遇到以下场景:
     var promise = new Promise((resolve, reject) => {
         setTimeout(() => {
             resolve('success')
         }, 1000)
     })
     promise.then(res => {
         console.log(res)
     }).then(res => {
         console.log(res)
     })
     上述场景中,resolve方法被异步调用了,根据JS的执行顺序,promise.then将会被先执行,
     而根据Promise规范,这里需要保证在Promise的状态被改变了之后再执行then中的方法,
     因此要将then中的方法保存到callback数组中,然后在上面的resolve或者reject调用之后再执行
     */
    if (this.promiseState === 'pending') {
    
      this.callback.push({
        onResolved: function () {
          /**
           这里的改造其实跟之前的改动是一样的,
           只不过是在resolve和reject在异步中调用的时候,then的回调会放到这里执行,
           在具体执行的时候,两边的执行逻辑其实是一样的,
           就是执行的时机修改了一下
          */
          try {
            var result = onResolved(self.promiseResult)
            resolvePromise(promise2, result, resolve, reject)
          } catch (error) {
            reject(error)
          }
        },
        onRejected: function () {
          try {
            var result = onRejected(self.promiseResult)
            resolvePromise(promise2, result, resolve, reject);
          } catch (error) {
            reject(error)
          }
        }
      })
    }
  })
  return promise2
}

/**
 catch方法只是单独的对reject方法执行后的处理,而这个处理在then方法中都有实现,
 因此只要调用then并将所需的参数传入就行,并且这时候catch会返回一个状态为fulfilled的promise
 所以在下列场景中,catch执行完成后还会继续执行then:
 new Promise((resolve, reject) => {
     reject('err')
 }).then(res => {
     console.log(res)
 }).catch(reason => {
     console.log(reason)
     return 'success'
 }).then(res => {
     console.log(res)
     return 'success2'
 }).then(res => {
     console.log(res)
 })
 输出为err, success, success2
 */
Promise.prototype.catch = function (onRejected) {
  return this.then(undefined, onRejected)
}

// 统一对then中onResolved和onRejected方法的返回值进行处理
function resolvePromise(promise2, x, resolve, reject) {

  // 判断传入的promise2和x是否相等,防止死循环
  if (promise2 === x) {
  
    // 相等的话就报错,放在reject中直接改变promise状态为rejected
    reject(new TypeError('Chaining cycle'))
  }
  
  // 当x不等于null,并且是object类型或者function类型时的情况
  if (x && typeof x === 'object' || typeof x === 'function') {
  
    // 定义一个常量used,判断
    let used
   
    // 捕获x.then这一步执行时候的错误,如果抛出错误就直接reject
    try {
    
      // 判断x是否存在then属性
      let then = x.then
      
      // 判断then是否是个函数
      if (typeof then === 'function') {
      
        /**
         如果then是个函数,那么通过call去执行它,并且将执行对象指定为x,两个参数通过封装后传入,
         这里就相当于改造x自己的then方法,让其像Promise自己提供的那个then方法一样执行
        */
        then.call(x, (y) => {
    
          /**
           这里为了防止x.then是一个thenable对象,这样就可能发生resolve和reject都被调用的情况
           所以要保证resolve和reject只能以先执行的那一个为准,类似以下情况,used的判断就有用了
           let promise = new Promise((resolve, reject) => {
               resolve()
           }).then(res => {
               return {
                  then: function(onResolved, onResolved) {
                    onResolved(3)
                    onResolved(5)
                  }
               }
            })
            当used为true,说明已经下述代码已经执行过一次了,因此不再执行
          */
          if (used) return
          used = true
          
          // 将参数y传入resolvePromise之后正常执行
          resolvePromise(promise2, y, resolve, reject)
        }, (r) => {
          if (used) return
          used = true
          reject(r)
        })
        
      } else {
        
        // 当x的then不是个函数时,就正常将x当作参数执行就可以了
        if (used) return
        used = true
        resolve(x)
      }
    } catch (e) {
      if (used) return
      used = true
      reject(e)
    }
  } else {
  
    /**
     当x不是对象或者函数的时候,就直接通过执行resolve(x)
     以此来改变执行完then后所返回新的promise的状态
     */
    resolve(x)
  }
}

Copy the code

Tests whether the specification is met

PromiseA+ / PromiseA+ / PromiseA+ / PromiseA+ / PromiseA+ / PromiseA+ / PromiseA+ / PromiseA+ / PromiseA+

  • First of all bynpm install promises-aplus-testsInstall dependencies
  • Edit the package.json file
{" name ":" the promise ", "version" : "1.0.0", "description" : "my promise", "main" : ". / promise. Js ", "scripts" : {" test ": "promises-aplus-tests promise" }, "author": "ITEM", "license": "ISC", "devDependencies": { "promises-aplus-tests": "^ 2.1.2"}}Copy the code
  • Add the following code to your promise.js file:
module.exports = {
  deferred() {
    var resolve;
    var reject;
    var promise = new Promise(function (res, rej) {
      resolve = res;
      reject = rej;
    })
    return {
      promise,
      resolve,
      reject
    }
  }
}
Copy the code
  • Run the NPM run test command

  • End result: a perfect fit

To summarize

  • A Promise that complies with the specification itself may not perform exactly the same as a built-in Promise, as in the following code:
new Promise((resolve, reject) => { resolve() }).then(() => { console.log(0); return new Promise((resolve, reject) => { resolve(4) }) }).then((res) => { console.log(res) }) new Promise((resolve, reject) => { resolve() }).then(() => { console.log(1); }).then(() => { console.log(2); }).then(() => { console.log(3); }).then(() => { console.log(5); }).then(() =>{ console.log(6); }) // browser execution: 0,1,2,3,4,5,6 // actual implementation execution: 0,1,2,4,3,5,6Copy the code
  • The implementation here satisfies the A+ specification, but falls short of A full Promise with built-in methods such as,all.raceEtc.