(给前端大全加星标,提升前端技能)
转自:Col0ring
juejin.cn/post/6886360224308035598
写在前面
没错,这又是一篇关于手写 Promise 的文章,想必大家已经看过很多相关 Promise 的文章,关于一些 Promise 出现原因等问题我就不详细说了,网上有很多资料。这次我们使用 typescript,从 ES6 中的 Promise 类型定义入手,分析 Promise 及相关方法的传入参数和返回值,手写一个 typescript 版本的 Promise。
Promise/A+ 规范
Promise/A+ 规范是业内所有的 Promise 类库的统一规范,我们要写的 Promise 也要符合这一规范(英文文档请查看 Promises/A+:https://promisesaplus.com/,相关中文翻译 Promise A+ 规范:http://malcolmyu.github.io/malnote/2015/06/12/Promises-A-Plus/,感谢译者)。
由于内容过多,在下面的编码中会一一进行实现,这里先提出几个术语:
- 解决(fulfill):指一个 promise 成功时进行的一系列操作,如状态的改变、回调的执行。虽然规范中用
fulfill
来表示解决,但在后世的 promise 实现多以resolve
来指代之。 - 拒绝(reject):指一个 promise 失败时进行的一系列操作。
- 终值(eventual value):所谓终值,指的是 promise 被解决(fulfill)时传递给解决回调的值,由于 promise 有一次性的特征,因此当这个值被传递时,标志着 promise 等待态的结束,故称之终值,有时也直接简称为值(value)。
- 据因(reason):也就是拒绝原因,指在 promise 被拒绝(reject) 时传递给拒绝回调的值。
值得注意的是,核心的 Promises/A+ 规范不设计如何创建、解决和拒绝 promise,而是专注于提供一个通用的 then
方法。所以,完成了对于then
方法的交互,其实也就基本完成了对于 Promises/A+ 规范的实现。
实现 Promise
基本功能
首先我们要实现 Promise 的基本功能,传入生成器,reslove
和reject
函数的执行以及调用then
函数对于值的基本获取,先来看看构造函数的类型定义:
// 这是类本身,还有 all, race 等方法在这里面定义
interface PromiseConstructor {
/**
* A reference to the prototype.
*/
readonly prototype: Promise<any>;
/**
* Creates a new Promise.
* @param executor A callback used to initialize the promise. This callback is passed two arguments:
* a resolve callback used to resolve the promise with a value or the result of another promise,
* and a reject callback used to reject the promise with a provided reason or error.
*/
// 这里就是重点部分了
new (executor: (resolve: (value?: T | PromiseLike) => void, reject: (reason?: any) => void) => void): Promise<T>;
// ...
}
上面我们会看到两个其余的接口类型,分别是Promise
和PromiseLike
,其中Promise
就是实例对象的相关属性接口:
// 这时实例对象,下面只是 ES2015 的接口属性,因为后续 Promise 做过更新,后续会说明更多实例属性
interface Promise {
/**
* Attaches callbacks for the resolution and/or rejection of the Promise.
* @param onfulfilled The callback to execute when the Promise is resolved.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of which ever callback is executed.
*/
then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise<TResult1 | TResult2>;
//...
}
PromiseLike
的接口在下面:
interface PromiseLike {
/**
* Attaches callbacks for the resolution and/or rejection of the Promise.
* @param onfulfilled The callback to execute when the Promise is resolved.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of which ever callback is executed.
*/
then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): PromiseLike<TResult1 | TResult2>;
}
可以看出PromiseLike
接口定义的就是一个拥有then()
方法的对象(官方的叫法是 thenable
),只要有then()
方法就会将其当做一个Promise
实例看待。
// 试验一下
new Promise((resolve) => {
resolve({
prop: 'common property',
// 这里我们自己构造了个 then 方法,Promise 会自动为 then 方法 reslove 和 reject 函数
then(reslove2: any) {
reslove2('promiselike')
}
})
}).then((res) => {
// 果然,被当做成了 Promise
console.log(res) // promiselike
})
需要注意的是,Promise 内部的回调函数的异步执行机制是使用的微任务,而我们所使用的环境中并没有为我们提供微任务的相关 api,所以代码中都是使用setTimeout
进行异步模拟,将回调直接推入到事件环的最后。
如果对事件环与微任务不太了解,可以查看下这篇文章 彻底搞懂 JS 事件轮询:https://juejin.im/post/6844904198581010439。
下面是代码实现:
// 创建一枚举类型保存响应状态的变量
enum Status {
PENDING = 'pending',
FULFILLED = 'fulfilled',
REJECTED = 'rejected'
}
// 将需要类型提出来
type Resolve = (value: T | PromiseLike) => voidtype Reject = (reason?: any) => voidtype Executor = (resolve: Resolve, reject: Reject) => voidtype onFulfilled =
| ((value: T) => TResult1 | PromiseLike)
| undefined
| nulltype onRejected<TResult2> =
| ((reason: any) => TResult2 | PromiseLike)
| undefined
| null
/*
将判断是否为 thenable 单独提出来,减少代码冗余,不然每次都需要使用:
((typeof value === 'object' && value !== null) ||typeof value === 'function') && typeof (value as PromiseLike).then === 'function'
来进行判断,同时也有更好的 typescript 提示
*/function isPromise(value: any): value is PromiseLike<any> {
return (
((typeof value === 'object' && value !== null) ||typeof value === 'function') &&typeof value.then === 'function')
}class MyPromise<T> {
// 刚开始的状态status: Status = Status.PENDING
// 保存当前 Promise 的终值,这里让它一定会有值private value!: T
// 保存当前 Promise 的据因private reason?: anyprivate onFulfilledCallback: (() => void)[] = [] //成功的回调private onRejectedCallback: (() => void)[] = [] //失败的回调constructor(executor: Executor) {
try {
// 防止 this 丢失executor(this._resolve.bind(this), this._reject.bind(this))
} catch (e) {
// 出错直接 rejectthis._reject(e)
}
}private _resolve(value: T | PromiseLike) {
try{
// 模拟微任务异步setTimeout(() => {
// 判断是否是个 thenable 对象,如果是,我们直接取 pending 结束后的值if (isPromise(value)) {
// 再次将内部的 resolve 和 reject 函数传入
value.then(this._resolve.bind(this), this._reject.bind(this))return
}// 如果是 pending 状态就变为 fulfilledif (this.status === Status.PENDING) {
this.status = Status.FULFILLED// 这里的 value 类型只会是 Tthis.value = value// resolve 后执行 .then 时传入的回调this.onFulfilledCallback.forEach((fn) => fn())
}
})
}catch(err){
// 捕获如果传入的是 Promise 时在内部抛出错误后的捕获this._reject(err)
}
}
// 内部的 reject 函数,就是我们实例 Promise 传入给用户调用的 rejectprivate _reject(reason: any) {
// 大体用法同上,这里不用进行值穿透,所以不用判断是否为 Promise 对象了setTimeout(() => {
if (this.status === Status.PENDING) {
this.status = Status.REJECTEDthis.reason = reasonthis.onRejectedCallback.forEach((fn) => fn())
}
})
}public then<TResult1 = T, TResult2 = never>(
onfulfilled?: onFulfilled,
onrejected?: onRejected): MyPromise<TResult1 | TResult2> {
// 关于 onfulfilled 与 onrejected 如果没有传我们需要进行值的透传,但是在基本功能的实现中我们先不管这个问题,默认一定会传入函数
// 判断当前状态,如果是异步 reslove 或 reject,那么此时的 status 还是 pendingif (this.status === Status.FULFILLED) {
setTimeout(() => {
onfulfilled!(this.value)
})
}if (this.status === Status.REJECTED) {
setTimeout(() => {
onrejected!(this.reason)
})
}if (this.status === Status.PENDING) {
// 如果为 pending,需要将 onFulfilled 和 onRejected 函数都存放起来,状态确定后再依次执行
// 执行回调的时候有 setTimeout,这里就不加了this.onFulfilledCallback.push(() => {
onfulfilled!(this.value)
})this.onRejectedCallback.push(() => {
onrejected!(this.reason)
})
}
// 链式调用,这段代码现在可以