当异步操作完成,过程成功或失败时需要有变量来存储结果,因此可以声明 value 来存储成功值,reason 来存储失败时的原因
function MyPromise(executor) {
let self = this
self.status = status.PENDING
self.value = null
self.reason = null
function resolve(value) {
if (self.status === status.PENDING) {
self.value = value
self.status = status.FULFILLED
}
}
function reject(reason) {
if (self.status === status.PENDING) {
self.reason = reason
self.status = status.REJECTED
}
}
try {
executor(resolve, reject)
} catch (err) {
reject(err)
}
}
.then()方法
规范约定了 Promise 有一个.then()方法,该方法有两个形参 onFulfilled,onRejected,二者都是 Function 类型。前者是在状态变为 fulfilled 时触发,接受存储成功结束异步操作结果的 value 作为参数,后者是在状态变为 rejected 时触发,接受异步操作有抛异常结果的 reason 作为参数。以此为据,实现一个基础版的 then 方法
MyPromise.prototype.then = function (onFulfilled, onRejected) {
let self = this
if (self.status === status.FULFILLED) {
onFulfilled(self.value)
}
if (self.status === status.REJECTED) {
onRejected(self.reason)
}
}
基础版 then 方法,其实只处理了同步改变状态的场景,若内部状态处于 pending,结束异步操作是在下一个 loop,那么在执行 then 时 self.value 或 self.reason 为 null,且 onFulfilled 和 onRejected 都没有触发时机。因此可利用订阅发布的思想,声明 onResolvedCallbacks 和 onRejectedCallbacks 两个数组来进一步完善 then 和 Promise 的构造函数
function MyPromise(executor) {
let self = this
self.status = status.PENDING
self.value = null
self.reason = null
self.onResolvedCallbacks = []
self.onRejectedCallbacks = []
function resolve(value) {
if (self.status === status.PENDING) {
self.value = value
self.status = status.FULFILLED
//状态改变代表异步操作结束,执行对应数组中订阅的回调函数
self.onResolvedCallbacks.forEach((onResolvedCallback) => {
onResolvedCallback()
})
}
}
function reject(reason) {
if (self.status === status.PENDING) {
self.reason = reason
self.status = status.REJECTED
//同resolve
self.onRejectedCallbacks.forEach((onRejectedCallback) => {
onRejectedCallback()
})
}
}
try {
executor(resolve, reject)
} catch (err) {
reject(err)
}
}
MyPromise.prototype.then = function (onFulfilled, onRejected) {
let self = this
//未结束异步操作时先订阅
if (self.status === status.PENDING) {
self.onFulfilledCallbacks.push(() => {
onFulfilled(self.value)
})
self.onRejectedCallbacks.push(() => {
onRejected(self.reason)
})
}
if (self.status === status.FULFILLED) {
onFulfilled(self.value)
}
if (self.status === status.REJECTED) {
onRejected(self.reason)
}
}
基于 A+规范的 Promise 实现
前言
JavaScript 作为单线程编程语言,在异步流程控制方面有其独特的解决方案,且更新换代的速度也是非常快,从最原始的回调函数,到 Promise、Generator 以及目前实际开发中常用的 async/await 语法糖,不管是易用性还是代码可读性都有了很大的提升。本文旨在参考Promise/A+规范(手动撸一个 Promise 前建议先浏览规范)实现一个基本满足规范要求的 Promise。什么是 Promise?建议参考阮一峰老师的es6 入门
骨架
Promise 构造函数接受一个可执行函数的参数,此函数有两个形参 resolve 和 reject,所接受的实参类型都是 Function。resolve 用来处理 Promise 正常返回时的值,reject 用来处理各类异常值,其定义以及实现是在 Promise 构造函数中,实例化过程中这个可执行函数会被执行。要注意的是,该函数执行要放在 try/catch 中以便异常捕获。
状态控制
Promise 对象内部有三个状态 pending,fulfilled,rejected,通俗地说依次就是正在进行中,成功,失败。规范约定了初始状态为 pending,只有 Promise 保存的异步操作的结果才能决定内部的最终状态,其他任何操作都不能改变该状态,且该状态一经改变就不会再变了。状态改变只有两种可能:从 pending 变为 fulfilled 和从 pending 变为 rejected。因此可声明三个常量来存储这三种状态。
异步结果处理
当异步操作完成,过程成功或失败时需要有变量来存储结果,因此可以声明 value 来存储成功值,reason 来存储失败时的原因
.then()方法
规范约定了 Promise 有一个.then()方法,该方法有两个形参 onFulfilled,onRejected,二者都是 Function 类型。前者是在状态变为 fulfilled 时触发,接受存储成功结束异步操作结果的 value 作为参数,后者是在状态变为 rejected 时触发,接受异步操作有抛异常结果的 reason 作为参数。以此为据,实现一个基础版的 then 方法
基础版 then 方法,其实只处理了同步改变状态的场景,若内部状态处于 pending,结束异步操作是在下一个 loop,那么在执行 then 时 self.value 或 self.reason 为 null,且 onFulfilled 和 onRejected 都没有触发时机。因此可利用订阅发布的思想,声明 onResolvedCallbacks 和 onRejectedCallbacks 两个数组来进一步完善 then 和 Promise 的构造函数
规范 2.2.7 小节描述 then 需要返回一个 promise,且对 onFulfilled 和 onRejected 对返回结果处理上也有要求。为了更加语义化,返回的 promise 命名为 returnPromise,onFulfilled 和 onRejected 的返回结果分别赋值给 success 和 failed 变量。规范要求 onFulfilled 和 onRejected 正常执行时返回的值要交给一个
Promise Resolution Procedure(Promise决议程序) [[Resolve]](promise2, x)
处理(先不关心这个决议程序具体实现,暂且声明其为 resolvePromise),若有抛出异常 e 则 returnPromise 被 rejected,且以 e 作为 reason。Promise Resolution Procedure(Promise 决议程序)
规范 2.3 针对 resolvePromise 进行了详细的描述,贴上该小节的一个中文版
对照该小节的要求实现 resolvePromise
.then()保持异步执行
规范 3.1 中有提到 then 需要保持异步执行,且属于 js 执行机制 event loop 中的为微任务(micro-task),本文中为了方便直接用宏任务 setTimeout 代替实现
完善
构造函数中的 resolve
一个实例 resolve 时接受的 value 参数若也是 Promise 那么本实例的内部状态改变会受 value 是否已经改变内部状态的影响,因此应该交给 then 处理
onFulfilled 和 onRejected 默认值处理
在实际应用中,会有如下情况
即 then 不传 onFulfilled 和 onRejected,但发现打印的日志仍能显示
ok
,可见 onFulfilled 和 onRejected 是有默认值的,这其实也是 Promise 值穿透的特性。上述代码等同于完善 then 方法,给 onFulfilled 和 onRejected 赋默认值
原型上的方法
原生 Promise 原型上还有很多方法,此处简单介绍几种
catch
在 then 以及 resolvePromise 所有处理异步操作结果的同步代码都写在了 try/catch 中,若有抛出异常都会被 returnPromise 的 reject 捕获,因此 catch 其实就是一个特殊的 then,通过这个特殊的 then 的 onRejected 捕获 reject 的错误或前面 Promise 的 onRejected 默认值 throw 的 reason
cancelable
实际开发中会有需求取消一个 Promise 的执行,可以借助一个辅助函数以及 Promise.race()来实现。外部可能通过执行 helper.cancel()提前改变状态以此“取消”promiseObj 的执行
stop
cancelable 是间接取消某个 Promise 执行,那如何取消一个 Promise 链式调用?出发点是只要返回一个永远处于 pending 状态的 Promise 就不会继续向下执行 then 或 catch 了,也就做到了停止链式调用
但也有缺点,所有链式调用的回调函数无法被垃圾回收了,造成内存资源浪费
done
then 方法中的 onFulfilled 是在 try/catch 中执行的,错误会被 catch 到,但是若后续没有 then 或 catch 了,这个错误无法被捕获,就没有任何异常,换种说法即
Promise 有可能会吃掉错误
。实现 done 方法作为 Promise 链式调用的最后一步,用来向全局抛出没有被 Promise 内部捕获的错误,并且不再返回一个 Promise,来处理错误被吃掉的问题
至此,一个基本符合规范的 Promise 已经实现,原型上还有一些常用的方法,例如 all、race、resolve、reject 等,在此不一一列出,可参考实现源码
完整代码
参考规范
Promise/A+规范