简单版 Promise/A+,通过官方872个测试用例
promise 标准
在实现 Promise 之前要清楚的是 JavaScript 中的 Promise 遵循了 Promises/A+ 规范,所以我们在编写 Promise 时也应当遵循这个规范,建议认真、仔细读几遍这个规范。最好是理解事件循环,这样对于理解js中的异步是怎么回事非常重要。
基本使用
Promise( (resolve, reject) {...} Promise((resolve, reject)=>'url'=>=>=>}).then(value =>((err)=>})
promise 是处理异步结果的一个对象,承若状态改变时调用对应的回调函数,resolve、reject用来改变promise 的状态,then 绑定成功、失败的回调。
环境准备
安装测试工具以及nodemon因为我们要在node环境调试自己写的promise
// nodemonnpm install nodemon -D// promise 测试工具npm install promises-aplus-tests -D
增加脚本命令
"testPromise": "promises-aplus-tests myPromise/promise3.js", "dev": "nodemon ./myPromise/index.js -i "
各自的路径改成自己的即可,这个在后面会用来测试。
基本架子
根据规范实现一个简单的promise,功能如下
promise的三种状态(PENDING、FULFILLED、REJECTED)
状态只能由 Pending 变为 Fulfilled 或由 Pending 变为 Rejected ,且状态改变之后不会在发生变化,会一直保持这个状态
绑定then的回调
返回成功、失败的值
一个promise 支持调用多次then
支持捕获异常
= 'PENDING'= 'FULFILLED'= 'REJECTED'.status =.value =.reason =.onResolveCallbacks =.onRejectedCallbacks == (value) => (.status ===.status =.value = .onResolveCallbacks.forEach(fn =>= (reason) => (.status ===.status =.reason = .onRejectedCallbacks.forEach(fn => (.status = .onResolveCallbacks.push(() =>.onRejectedCallbacks.push(() => (.status === (.status ==== myPromise
订阅
传进来的fn是一个执行器,接受resolve、reject参数,通常我们在构造函数中需要调用某个接口,这是一个异步的操作,执行完构造函数之后,在执行then(),这个时候的状态还是pending,所以我们需要把then 绑定的回调存起来,也可以理解为promise对象订阅了这个回调。
发布
在 resolve,reject函数中中我们改变了promise 对象的状态,既然状态改变了,那么我们需要执行之前订阅的回调,所以在不同的状态下执行对应的回调即可。
流程
如上所示,实例化对象,执行构造函数,碰到异步,挂起,然后执行then()方法,绑定了resolve、reject的回调。如果异步有了结果执行对应的业务逻辑,调用resolve、或者reject,改变对应的状态,触发我们绑定的回调。
以上就是最基本的promise架子,但是还有promise 调用链没有处理,下面继续完善...
完善promise 调用链
promose 的精妙的地方就是这个调用链,首先then 函数会返回一个新的promise 对象,并且每一个promise 对象又有一个then 函数。惊不惊喜原理就是那么简单,回顾下then的一些特点
then 特点
then 返回一个新的promise 对象
then 绑定的回调函数在异步队列中执行(evnet loop 事件循环)
通过return 来传递结果,跟fn一样如果没有return,默认会是 underfined
抛出异常执行绑定的失败函数(最近的promise),如果没有,则执行catch
then中不管是不是异步只要resolve、rejected 就会执行对应 onFulfilled、onRejected 函数
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 等对于这部分规范文档也有详细的说明
[[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
完善其他方法
all
allSettled
any
race
catch
finlly
npm run dev // 可以用来测试这些方法
源码
比较官方的源码: https://github.com/then/promise
参考
https://www.jianshu.com/p/4d266538f364
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/all