简单版 Promise/A+,通过官方872个测试用例

  • A+
所属分类:Web前端
摘要

在实现 Promise 之前要清楚的是 JavaScript 中的 Promise 遵循了 Promises/A+ 规范,所以我们在编写 Promise 时也应当遵循这个规范,建议认真、仔细读几遍这个规范。最好是理解事件循环,这样对于理解js中的异步是怎么回事非常重要。


promise 标准

在实现 Promise 之前要清楚的是 JavaScript 中的 Promise 遵循了 Promises/A+ 规范,所以我们在编写 Promise 时也应当遵循这个规范,建议认真、仔细读几遍这个规范。最好是理解事件循环,这样对于理解js中的异步是怎么回事非常重要。

https://promisesaplus.com/

基本使用

new Promise( function(resolve, reject) {...} /* executor */  );
new Promise((resolve, reject)=> { AjaxRequest.post({ url: 'url', data: {}, sueccess: ()=> { resolve(res) }, fail: (err)=> { reject(err) } }) }).then((res)=> { // do some }).then(value => { }).catch((err)=> { // do some })

promise 是处理异步结果的一个对象,承若状态改变时调用对应的回调函数,resolve、reject用来改变promise 的状态,then 绑定成功、失败的回调。

环境准备

安装测试工具以及nodemon因为我们要在node环境调试自己写的promise

// nodemon npm install nodemon -D // promise 测试工具 npm install promises-aplus-tests -D 

增加脚本命令

"testPromise": "promises-aplus-tests myPromise/promise3.js",  "dev": "nodemon ./myPromise/index.js -i "

各自的路径改成自己的即可,这个在后面会用来测试。

基本架子

根据规范实现一个简单的promise,功能如下

  1. promise的三种状态(PENDING、FULFILLED、REJECTED)
  2. 状态只能由 Pending 变为 Fulfilled 或由 Pending 变为 Rejected ,且状态改变之后不会在发生变化,会一直保持这个状态
  3. 绑定then的回调
  4. 返回成功、失败的值
  5. 一个promise 支持调用多次then
  6. 支持捕获异常
/*   基本架子 根据promise A+ 规范还要处理then链式调用以及返回值传递的问题,后续在promise2、promise3 处理  */  const PENDING = 'PENDING',       FULFILLED = 'FULFILLED',       REJECTED = 'REJECTED';  class myPromise {   constructor (executor) {     this.status = PENDING     this.value = undefined     this.reason = undefined
this.onResolveCallbacks = [] this.onRejectedCallbacks = [] const resolve = (value) => { if (this.status === PENDING) { this.status = FULFILLED this.value = value // 发布 this.onResolveCallbacks.forEach(fn => fn()) } } const reject = (reason) => { if (this.status === PENDING) { this.status = REJECTED this.reason = reason // 发布 this.onRejectedCallbacks.forEach(fn => fn()) } } try { // 执行传进来的fn, 在给他提供改变状态的fn executor(resolve, reject) } catch(e) { reject(e) } } // 订阅回调函数 then (onFulfilled, onRejected) { if (this.status = PENDING) { // 订阅 this.onResolveCallbacks.push(() => { onFulfilled(this.value) }) this.onRejectedCallbacks.push(() => { onRejected(this.reason) }) } if (this.status === FULFILLED) { onFulfilled(this.value) } if (this.status === REJECTED) { onRejected(this.reason) } } } module.exports = myPromise

订阅

   传进来的fn是一个执行器,接受resolve、reject参数,通常我们在构造函数中需要调用某个接口,这是一个异步的操作,执行完构造函数之后,在执行then(),这个时候的状态还是pending,所以我们需要把then 绑定的回调存起来,也可以理解为promise对象订阅了这个回调。

发布

   在 resolve,reject函数中中我们改变了promise 对象的状态,既然状态改变了,那么我们需要执行之前订阅的回调,所以在不同的状态下执行对应的回调即可。

流程

简单版 Promise/A+,通过官方872个测试用例

如上所示,实例化对象,执行构造函数,碰到异步,挂起,然后执行then()方法,绑定了resolve、reject的回调。如果异步有了结果执行对应的业务逻辑,调用resolve、或者reject,改变对应的状态,触发我们绑定的回调。

以上就是最基本的promise架子,但是还有promise 调用链没有处理,下面继续完善...

完善promise 调用链

promose 的精妙的地方就是这个调用链,首先then 函数会返回一个新的promise 对象,并且每一个promise 对象又有一个then 函数。惊不惊喜原理就是那么简单,回顾下then的一些特点

then 特点

  1. then 返回一个新的promise 对象
  2. then 绑定的回调函数在异步队列中执行(evnet loop 事件循环)
  3. 通过return 来传递结果,跟fn一样如果没有return,默认会是 underfined
  4. 抛出异常执行绑定的失败函数(最近的promise),如果没有,则执行catch
  5. then中不管是不是异步只要resolve、rejected 就会执行对应 onFulfilled、onRejected 函数
  6. then中返回promise状态跟执行回调的结果有关,如果没有异常则是FULFILLED,就算没有retun 也是FULFILLED,值是underfined,有异常就是REJECTED,接着走下个then 绑定的onFulfilled 、onRejected 函数

   根据上面的特点以及阅读规范我们知道then()函数主要需要处理以下几点

  • 返回一个新的promise
  • 值怎么传给then返回的那个promise
  • 状态的改变

返回一个新的promise

因为promise 的链式调用涉及到状态,所以then 中返回的promise 是一个新的promise

then(onFulfilled, onRejected) {    let promise2 = new Promise((resolve, reject) => {      // do ...    })    return promise2  }

值的传递、状态的改变

let p = new myPromise((resolve, rejected) => {   // do ... }) p.then(   value => {     return 1   },   reason => {}   )   .then(     value => {       return new Promise((resolve, rejected) => {         resolve('joel')       })     },     reason => {}     )   .then(     value => {       throw 'err: 出错啦'     },     reason => {}     )

then 返回的值可能是一个普通值、promise对象、function、error 等对于这部分规范文档也有详细的说明

简单版 Promise/A+,通过官方872个测试用例

[[Resolve]](promise, x)

这个可以理解为promise 处理的过程,其中x是执行回调的一个值,promise 是返回新的promise对象,完整代码如下

我们将这部分逻辑抽成一个独立的函数 如下

// 处理then返回结果的流程 function resolvePromise(promise2, x, resolve, reject) {   if (promise2 === x) {     return reject(new TypeError('Chaining cycle detected for promise #<myPromise>'))   }    let called = false    if ((typeof x === 'object' && x !== null) || typeof x === 'function') {     try {       let then = x.then       // 判断是否是promise       if (typeof then === 'function') {         then.call(x, (y) => {           // 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)           if (called) return           called = true           resolvePromise(promise2, y, resolve, reject)         }, (r) => {           if (called) return           called = true           reject(r)         })       } else {         resolve(x)       }     } catch (e) {       if (called) return       called = true       reject(e)     }   } else {     // 如果 x 不为对象或者函数,以 x 普通值执行回调     resolve(x)   } }

测试

promises-aplus-tests 这个工具我们必须实现一个静态方法deferred,官方对这个方法的定义如下:

deferred: 返回一个包含{ promise, resolve, reject }的对象

promise 是一个处于pending状态的promise

resolve(value) 用value解决上面那个promise

reject(reason) 用reason拒绝上面那个promise

添加如下代码

myPromise.defer = myPromise.deferred = function () {   let deferred = {}    deferred.promise = new myPromise((resolve, reject) => {     deferred.resolve = resolve     deferred.reject = reject   })   return deferred }

在编辑执行我们前面加的命令即可

npm run testMyPromise

简单版 Promise/A+,通过官方872个测试用例

 

完善其他方法

  1. all
  2. allSettled
  3. any
  4. race
  5. catch
  6. finlly
npm run dev // 可以用来测试这些方法

简单版 Promise/A+,通过官方872个测试用例

源码

源码

比较官方的源码: https://github.com/then/promise 

参考

https://promisesaplus.com/

https://www.jianshu.com/p/4d266538f364

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/all