实现一个符合 Promise/A+规范的 Promise(typescript 版)
(给前端大学加星标,提升前端技能.)
转自: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 ) => void
type Reject = (reason?: any) => void
type Executor = (resolve: Resolve, reject: Reject ) => void
type onFulfilled =
| ((value: T) => TResult1 | PromiseLike )
| undefined
| null
type 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?: any
private onFulfilledCallback: (() => void)[] = [] //成功的回调
private onRejectedCallback: (() => void)[] = [] //失败的回调
constructor(executor: Executor ) {
try {
// 防止 this 丢失
executor(this._resolve.bind(this), this._reject.bind(this))
} catch (e) {
// 出错直接 reject
this._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 状态就变为 fulfilled
if (this.status === Status.PENDING) {
this.status = Status.FULFILLED
// 这里的 value 类型只会是 T
this.value = value
// resolve 后执行 .then 时传入的回调
this.onFulfilledCallback.forEach((fn) => fn())
}
})
}catch(err){
// 捕获如果传入的是 Promise 时在内部抛出错误后的捕获
this._reject(err)
}
}
// 内部的 reject 函数,就是我们实例 Promise 传入给用户调用的 reject
private _reject(reason: any) {
// 大体用法同上,这里不用进行值穿透,所以不用判断是否为 Promise 对象了
setTimeout(() => {
if (this.status === Status.PENDING) {
this.status = Status.REJECTED
this.reason = reason
this.onRejectedCallback.forEach((fn) => fn())
}
})
}
public then<TResult1 = T, TResult2 = never>(
onfulfilled?: onFulfilled,
onrejected?: onRejected
): MyPromise<TResult1 | TResult2> {
// 关于 onfulfilled 与 onrejected 如果没有传我们需要进行值的透传,但是在基本功能的实现中我们先不管这个问题,默认一定会传入函数
// 判断当前状态,如果是异步 reslove 或 reject,那么此时的 status 还是 pending
if (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)
})
}
// 链式调用,这段代码现在可以直接无视,为了不让 ts 类型报错加的,因为 .then 返回一个的 Promise 的值是依赖上一个 Promise 的状态和结果的
return new MyPromise(() => {})
}
}
OK,上面已经完成了一个只有一条链的 Promise,下面做一下测试:
// 同步
new MyPromise((reslove, reject) => {
reslove('success')
}).then(
(res) => {
console.log(res) // success
},
(err) => {
console.log(err)
}
)
// 异步
new MyPromise((reslove, reject) => {
setTimeout(() => {
reslove('timeout success')
}, 2000)
}).then(
(res) => {
console.log(res) // timeout success
},
(err) => {
console.log(err)
}
)
结果为立刻打印success
,两秒后打印timeout success
,符合我们的预期。
then 的深入(重点)
就如前面所说,Promise/A+ 规范的整个核心都在于对于then
方法的处理。并且还有第三方测试库 promises-aplus-tests:https://github.com/promises-aplus/promises-tests 测试我们所写的 Promise 是否符合规范,我们后面也会用使用这个测试库进行测试。
链式调用
要实现then
的链式调用,需要返回一个新的 Promise,同时不管在then
中回调函数onfulfilled
和onrejected
返回了什么值,都可以在这个新的 Promise 的then
方法的回调函数参数中得到。
我们用x
来作为then
方法中传入的onfulfilled
或onrejected
的返回值,用promise
来表示then
方法返回的那个新的 Promise,依据 Promise/A+ 规范,我们应该对这段解决过程 [[Resolve]](promise, x)
做如下操作:
x
与promise
相等:如果promise
和x
指向同一对象,以TypeError
为据因拒绝执行promise
。x
为 Promise:如果x
为 Promise ,则使promise
接受x
的状态。
如果 x
处于等待态,promise
需保持为等待态直至x
被执行或拒绝如果 x
处于执行态,用相同的值执行promise
如果 x
处于拒绝态,用相同的据因拒绝promise
x
为对象或函数
如果 x
为对象或者函数:1. 把x.then
赋值给then
2. 如果取x.then
的值时抛出错误e
,则以e
为据因拒绝promise
3. 如果then
是函数,将x
作为函数的作用域this
调用之。传递两个回调函数作为参数,第一个参数叫做resolvePromise
,第二个参数叫做rejectPromise
如果 resolvePromise
以值y
为参数被调用,则运行[[Resolve]](promise, y)
(就是继续递归这段解决过程)如果 rejectPromise
以据因r
为参数被调用,则以据因r
拒绝promise
如果 resolvePromise
和rejectPromise
均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
如果调用 then
方法抛出了异常e
:
如果 resolvePromise
或rejectPromise
已经被调用,则忽略之否则以 e
为据因拒绝promise
4. 如果then
不是函数,以x
为参数执行promise
如果 x
不为对象或者函数,以x
为参数执行promise
大体的流程图:
虽然上面看着有点多,其实大体来说就是让我们符合以下规则:
如果 then 中的回调函数返回一个值(非 Promise 实例或是 thenable 对象)或没有返回值(也就是返回 undefined),那么 then 返回的 Promise 将会成为接受状态,并且将返回的值作为接受状态的回调函数(
onfulfilled
)的参数值。new Promise<void>((reslove) => {
reslove()
})
.then(() => {
return 'success'
})
.then((res) => {
console.log(res) // success
})
复制代码如果 then 中的回调函数抛出一个错误,那么 then 返回的 Promise 将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数(
onrejected
)的参数值。new Promise<void>((reslove) => {
reslove()
})
.then(() => {
throw new Error('error message')
})
.then(
() => {},
(err) => {
console.log(err) // Error: error message
}
)循环返回 Promise 抛出错误就像下面这样 :
const promise2: Promise<any> = new Promise<void>((reslove) => {
reslove()
}).then(() => {
return promise2
})
promise2.then(() => {}, console.log) // [TypeError: Chaining cycle detected for promise #<Promise>]如果 then 中的回调函数返回一个已经是接受状态(fulfilled)的 Promise(我们这里暂且叫做
promise1
),那么 then 返回的 Promise 也会成为接受状态,并且将promise1
中then
的回调函数的参数值,作为该被返回的 Promise 的接受状态回调函数的参数值。new Promise<void>((reslove) => {
reslove()
})
.then(() => {
return Promise.reslove('success')
})
.then((res) => {
console.log(res) // success
})如果 then 中的回调函数返回一个已经是拒绝状态(rejected)的 Promise(这里我们暂且叫做
promise2
),那么 then 返回的 Promise 也会成为拒绝状态,并且将promise2
中then
的回调函数的参数值,作为该被返回的 Promise 的拒绝状态回调函数的参数值。new Promise<void>((reslove) => {
reslove()
})
.then(() => {
return new Promise.reject('error message')
})
.then(
() => {},
(err) => {
console.log(err) // error message
}
)如果 then 中的回调函数返回一个未定状态(pending)的 Promise(这里我们暂且叫做
promise3
),那么 then 返回 Promise 的状态也是未定的,并且它的终态与promise3
的终态相同。同时它变为终态时调用then
的回调函数参数与promise3
变为终态时的回调函数的参数是相同的。new Promise<void>((reslove) => {
reslove()
})
.then(() => {
return new Promise((reslove, reject) => {
setTimeout(()=>{
reslove('delay')
}, 2000)
})
})
.then(
res => {
console.log(res) // 两秒后打印 delay
}
)
好了,照着上面的解决过程 ,我们来写一下这个处理函数:
function resolvePromise<T>(
promise2: MyPromise,
x: T | PromiseLike,
resolve: Resolve,
reject: Reject
) {
// 不能引用同一个对象,不然会无限循环的
if (promise2 === x) {
const e = new TypeError(
'TypeError: Chaining cycle detected for promise #'
)
// 清空栈信息,不太清楚为什么 Promise 要清除这个,先不管了,继续往下
e.stack = ''
// 直接进入错误的回调
return reject(e)
}
let called = false // 防止多次调用
// 如果 x 为 Promise,通过上面的知识我们知道判断是否是个 Promise 或者像 Promise 我们是判断一个对象是否有 then 方法,可以发现在下面判断是否是对象或者函数中也有相同的判断,所以这里我们可以直接省略
// 如果 x 是对象或函数
if ((typeof x === 'object' && x != null) || typeof x === 'function') {
try {
/*
存储了一个指向 x.then 的引用,然后测试并调用该引用,以避免多次访问 x.then 属性。这种预防措施确保了该属性的一致性,因为其值可能在检索调用时被改变。
注:这里可以用我们封装的判断方法 isPromise 判断,但是既然跟着解决过程走,那么还是老老实实操作一下吧
*/
// 手动转一下类型
const then = (x as PromiseLike).then
if (typeof then === 'function') {
// 这里其实就是调用传入的 Promise 的 then 方法,下面代码就是执行了 x.then(()=>{},()=>{})
then.call(
x,
(y) => {
if (called) return
called = true
// 如果是 Promise,我们应该递归地获取到最终状态的值,传入相同的处理函数,不论是成功还是失败都能直接抛出到最外层
resolvePromise(promise2, y, resolve, reject)
},
(r) => {
if (called) return
called = true
// 如果传入的 Promise 被拒绝,直接抛出到最外层
reject(r)
}
)
} else {
// 不是 Promise 对象,当做普通值处理
resolve(x)
}
} catch (e) {
// 如果中间有错误。直接变为拒绝态
// 但是如果出现错误之前已经改变了状态,那么久不用管
if (called) return
called = true
reject(e)
}
} else {
// 普通值处理
resolve(x)
}
}
可以将上面的处理过程的代码实现与描述一步步带入查看,基本上都是能吻合的。
下面将其带入到then
中:
class MyPromise {
// ...
public then(
onfulfilled?: onFulfilled,
onrejected?: onRejected
): MyPromise {
// 现在我们将这个新生成的 Promise 和现在的 Promise 相互联系
const promise2 = new MyPromise((resolve, reject) => {
if (this.status === Status.FULFILLED) {
setTimeout(() => {
try {
// 获取到 x,然后与要返回的 Promise 产生联系
let x = onfulfilled!(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
if (this.status === Status.REJECTED) {
setTimeout(() => {
try {
// 获取到 x,然后与要返回的 Promise 产生联系
let x = onrejected!(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
if (this.status === Status.PENDING) {
// 如果为 pending,需要将 onFulfilled 和 onRejected 函数都存放起来,状态确定后再依次执行
// 执行回调的时候有 setTimeout,这里就不加了
this.onFulfilledCallback.push(() => {
try {
let x = onfulfilled!(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
this.onRejectedCallback.push(() => {
try {
let x = onrejected!(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
})
return promise2
}
//...
}
测试一下:
new MyPromise<void>((resolve) => {
resolve()
})
.then(() => {
return 'step1'
})
.then((res) => {
return res + ':' + 'step2'
})
.then((res) => {
console.log(res) // step1:step2
})
很好,完美符合预期。
值的穿透
之前我们使用then
时都是假设我们一定会向then
中传入回调函数,但是事实上在 Promise/A+ 规范中两个回调函数都是可以缺省的,这也是为什么我会在后面加上?
。当我们不向其传入回调函数时,此时就会触发值的穿透效果。
// 就像下面这样
new Promise((reslove) => {
reslove('hello')
})
.then()
.then()
.then()
.then((res) => {
console.log(res) // 'hello'
})
所以我们需要改造一下我们的then
函数,改造方法其实非常简单:
class MyPromise {
// ...
public then(
onfulfilled?: onFulfilled,
onrejected?: onRejected
): MyPromise {
// 如果传入的不是函数,就进行值的穿透,成功回调是返回相同的值,失败的回调是直接抛出错误
// 注意这里不能直接给上面传入的参数添加默认值,因为需要判断是否是函数
const onfulfilledFn =
typeof onfulfilled === 'function'
? onfulfilled
: (v: T | TResult1) => v as TResult1
const onrejectedFn =
typeof onrejected === 'function'
? onrejected
: (e: any) => {
throw e
}
// 将下面的 onfulfilled 改成 onfulfilledFn,onrejected 改成 onrejectedFn 就行了
// 现在我们将这个新生成的 Promise 和现在的 Promise 相互联系
const promise2 = new MyPromise((resolve, reject) => {
if (this.status === Status.FULFILLED) {
setTimeout(() => {
try {
// 获取到 x,然后与要返回的 Promise 产生联系
let x = onfulfilledFn(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
if (this.status === Status.REJECTED) {
setTimeout(() => {
try {
// 获取到 x,然后与要返回的 Promise 产生联系
let x = onrejectedFn(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
if (this.status === Status.PENDING) {
// 如果为 pending,需要将 onFulfilled 和 onRejected 函数都存放起来,状态确定后再依次执行
// 执行回调的时候有 setTimeout,这里就不加了
this.onFulfilledCallback.push(() => {
try {
let x = onfulfilledFn(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
this.onRejectedCallback.push(() => {
try {
let x = onrejectedFn(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
})
return promise2
}
//...
}
好了,看到了这里,恭喜你,已经完成了 Promise 最重要的部分,后续的所有 api 基本都是围绕着上面所写的部分展开的。
规范测试
使用我们前面提到的测试 Promise 规范的第三方库来进行测试:
npm install promises-aplus-tests -D
# or
yarn add promises-aplus-tests -D
复制代码
// 在文件末尾加上
// 忽略 typescript 校验
// @ts-ignore
MyPromise.defer = MyPromise.deferred = function () {
let dfd: any = {}
dfd.promise = new MyPromise((resolve, reject) => {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
export = MyPromise
然后使用tsc
命令将ts
文件编译,运行npx promises-aplus-tests 编译后的js文件位置
。
遗憾的是,我们的 Promise 并没有通过所有的测试,但是我们可以看到这 16 个未通过测试的报错都相同,调用处理的回调超时(超过了 200 ms 的延时),我的个人理解是由于setTimeout
这个 api 不能完全模拟微任务而造成的延时效果(说错了请大佬们轻点喷)。
如果要单看测试结果来说,想全部通过测试我们可以把最开始定义的 Promise 内部的reslove
函数做一个修改:
class MyPromise{
private _resolve(value: T | PromiseLike) {
try{
setTimeout(() => {
/*
删除下面这段代码就可通过全部测试:
if (isPromise(value)) {
value.then(this._resolve.bind(this), this._reject.bind(this))
return
}
*/
if (this.status === Status.PENDING) {
this.status = Status.FULFILLED
this.value = value as T // 强制转换类型
this.onFulfilledCallback.forEach((fn) => fn())
}
})
}catch(err){
this._reject(err)
}
}
}
当然,虽然通过了全部测试,但是很明显并不是符合预期的结果,当我们使用一个PromiseLike
对象时与真实的 Promise 结果并不一致:
// 还是用最开始的那个例子
new Promise((resolve) => {
resolve({
prop: 'common property',
then(reslove2: any) {
reslove2('promiselike')
}
})
}).then((res) => {
// 真实的 Promise 这里是 promiselike
console.log(res) // { prop: 'common property', then: [Function: then] }
})
所以,就结果而言,也算是基本合格吧。
Promise 的拓展方法
在前面我们已经完成了 Promise 的核心部分,现在可以依靠之前的代码再次完善我们的 Promise。
注: 在 Promise 的接口定义里,所有的实例方法都是定义在Promise
接口中的,所有的静态方法都是定义在PromiseConstructor
接口中的。
Promise.prototype.catch
Promise.prototype.catch(onrejected)
方法返回一个Promise,并且处理拒绝的情况。它的行为与调用Promise.prototype.then(undefined, onRejected)
相同。
接口类型:
interface Promise {
/**
* Attaches a callback for only the rejection of the Promise.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of the callback.
*/
catch(onrejected?: ((reason: any) => TResult | PromiseLike ) | undefined | null): Promise<T | TResult>;
}
实现代码:
interface MyPromise {
// 事实上它内部就是这样调用的
public catch(
onrejected?: onRejected
): MyPromise {
return this.then(null, onrejected)
}
}
Promise.resolve
Promise.resolve(value)
方法返回一个以给定值解析后的 Promise 对象。
如果这个值是一个 Promise 实例 ,那么将返回这个 Promise 实例。 如果这个值是 thenable
,会一直跟随thenable
拿到它的最终状态。如果都不是上面的值,返回的 Promise 实例将用这个值作为成功状态的终值。
接口类型:
interface PromiseConstructor {
// 可以看到有两种函数体,所以我们需要进行函数重载的定义
/**
* Creates a new resolved promise.
* @returns A resolved promise.
*/
resolve(): Promise<void>;
/**
* Creates a new resolved promise for the provided value.
* @param value A promise.
* @returns A promise whose internal state matches the provided promise.
*/
resolve(value: T | PromiseLike): Promise;
}
实现代码:
interface MyPromise {
// 函数重载
static resolve(): MyPromise<void>
static resolve(value: T | PromiseLike): MyPromise
// 最后的函数实体需要同时支持上面两种函数重载的类型,所以我们变成可选值
static resolve(value?: T | PromiseLike): MyPromise {
// 如果是 Promise,直接返回当前 Promise
if (value instanceof MyPromise) {
return value
}
return new MyPromise((resolve) => {
// 我们在内部已经做了对 thenable 的处理了,所以直接 reslove
// 因为必须传值,所以这里就强制推断了
resolve(value!)
})
}
}
Promise.reject
Promise.reject(reason)
方法返回一个带有拒绝原因的Promise
对象。
接口类型:
interface PromiseConstructor {
/**
* Creates a new rejected promise for the provided reason.
* @param reason The reason the promise was rejected.
* @returns A new rejected Promise.
*/
reject(reason?: any): Promise;
}
实现代码:
interface MyPromise {
static reject(reason?: any): MyPromise {
// 不需要额外判断
return new MyPromise((resolve, reject) => {
reject(reason)
})
}
}
Promise.all
Promise.all(iterable)
方法接收一个iterable
对象,返回一个 Promise 实例,此实例在 iterable
参数内所有的 thenable
状态都为fulfilled
或参数中不包含 thenable
时状态为fulfilled
,并且reslove
一个包含了所有传入thenable
的reslove
值的数组。如果参数内的 thenable
有一个状态为rejected
,此实例状态也为rejected
,并且reject
第一个失败 thenable
的结果。否者如果有thenable
的状态为pending
,此实例的状态也为pending
。
接口定义:
interface PromiseConstructor {
/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
* resolve, or rejected when any Promise is rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
all(values: readonly [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike, T5 | PromiseLike, T6 | PromiseLike, T7 | PromiseLike, T8 | PromiseLike, T9 | PromiseLike, T10 | PromiseLike]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]>;
all(values: readonly [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike, T5 | PromiseLike, T6 | PromiseLike, T7 | PromiseLike, T8 | PromiseLike, T9 | PromiseLike]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8, T9]>;
all(values: readonly [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike, T5 | PromiseLike, T6 | PromiseLike, T7 | PromiseLike, T8 | PromiseLike]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8]>;
all(values: readonly [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike, T5 | PromiseLike, T6 | PromiseLike, T7 | PromiseLike]): Promise<[T1, T2, T3, T4, T5, T6, T7]>;
all(values: readonly [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike, T5 | PromiseLike, T6 | PromiseLike]): Promise<[T1, T2, T3, T4, T5, T6]>;
all(values: readonly [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike, T5 | PromiseLike]): Promise<[T1, T2, T3, T4, T5]>;
all(values: readonly [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike]): Promise<[T1, T2, T3, T4]>;
all(values: readonly [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike]): Promise<[T1, T2, T3]>;
all(values: readonly [T1 | PromiseLike, T2 | PromiseLike]): Promise<[T1, T2]>;
all(values: readonly (T | PromiseLike)[]): Promise;
// 看着有点多,其实上面都是表示传入参数是一个数组的情况,这样写是因为传入的 Promise 中的 T 可能不同而重载不同元组类型
// see: lib.es2015.iterable.d.ts
all(values: Iterable>): Promise;
}
在类型定义中我们可以看到,当我们传入给Promise.all()
一个迭代器(Iterable
)的时候参数也是正确的,而数组本质也是一个迭代器,所以我们的编码操作可以完全围绕着迭代器进行展开。
实现代码:
interface MyPromise {
static all(
values: readonly [
T1 | PromiseLike,
T2 | PromiseLike,
T3 | PromiseLike,
T4 | PromiseLike,
T5 | PromiseLike,
T6 | PromiseLike,
T7 | PromiseLike,
T8 | PromiseLike,
T9 | PromiseLike,
T10 | PromiseLike
]
): MyPromise<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]>
// .....太占篇幅,省略了
static all(values: Iterable>): MyPromise
// 我们这里的实际实现也完全按照按照迭代器来实现就行了
static all(values: Iterable>): MyPromise {
return new MyPromise((resolve, reject) => {
// PromiseLike 对象会跟踪转换为 T
const resultArr: T[] = []
// 判断是否已经全部完成了
const doneArr: boolean[] = []
// 获取迭代器对象
let iter = values[Symbol.iterator]()
// 获取值 {value:xxx, done: false}
let cur = iter.next()
// 判断迭代器是否迭代完毕同时将最后得到的值放入结果数组中
const resolveResult = (value: T, index: number, done?: boolean) => {
resultArr[index] = value
doneArr[index] = true
if (done && doneArr.every((item) => item)) {
resolve(resultArr)
}
}
for (let i = 0; !cur.done; i++) {
const value = cur.value
doneArr.push(false)
cur = iter.next()
if (isPromise(value)) {
value.then((value: T) => {
resolveResult(value, i, cur.done)
}, reject)
} else {
resolveResult(value, i, cur.done)
}
}
})
}
Promise.race
Promise.race(iterable)
方法接收一个iterable
对象,返回一个 Promise,一旦迭代器中的某个thenable
的状态变为fulfiled
或rejected
,该实例的状态就会变成fulfiled
或rejected
。
接口定义:
interface PromiseConstructor {
/**
* Creates a Promise that is resolved or rejected when any of the provided Promises are resolved
* or rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
race(values: readonly T[]): Promiseextends PromiseLike ? U : T>;
// see: lib.es2015.iterable.d.ts
race(values: Iterable): Promiseextends PromiseLike ? U : T>;
}
代码实现:
class MyPromise {
static race(
values: Iterable
): MyPromiseextends PromiseLike ? U : T>
static race(
values: readonly T[]
): MyPromiseextends PromiseLike ? U : T>
// 还是直接使用迭代器
static race(
values: Iterable
): MyPromiseextends PromiseLike ? U : T> {
return new MyPromise((resolve, reject) => {
const iter = values[Symbol.iterator]()
let cur = iter.next()
while (!cur.done) {
const value = cur.value
cur = iter.next()
if (isPromise(value)) {
value.then(resolve, reject)
} else {
// 普通值,这时的值为 T,但是 Typescript 无法再深度判断了,需要自己手动转换
resolve(value as T extends PromiseLike ? U : T)
}
}
})
}
}
Promise.prototype.finally
ES2018
提出
Promise.prototype.finally(onfinally)
方法返回一个新的 Promise,并且该 Promise 的状态为 Promise 链条中前一个 Promise 的状态。在上一个 Promise 结束时,无论结果状态是fulfilled
或者是rejected
,都会执行指定的回调函数。 这为在Promise
是否成功完成后都需要执行的代码提供了一种方式。避免了同样的语句需要在then()
和catch()
中各写一次的情况。
具体用法:
// 不使用 finally
new Promise((resolve) => {
resolve()
}).then(() => {
console.log('success')
console.log('finally')
})
.catch(() => {
console.log('error')
console.log('finally')
})
// 使用 finally
new Promise((resolve) => {
resolve()
}).then(() => {
console.log('success')
})
.catch(() => {
console.log('error')
})
.finally(() => {
console.log('finally')
})
接口定义:
interface Promise {
/**
* Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The
* resolved value cannot be modified from the callback.
* @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).
* @returns A Promise for the completion of the callback.
*/
finally(onfinally?: (() => void) | undefined | null): Promise<T>
}
代码实现:
class MyPromise {
// 无论如何都会执行
public finally(onfinally?: onFinally): MyPromise {
return this.then(
(value) =>
MyPromise.resolve(
// 如果 onfinally 返回的是一个 thenable 也会等返回的 thenable 状态改变才会进行后续的 Promise
typeof onfinally === 'function' ? onfinally() : onfinally
).then(() => value),
(reason) =>
MyPromise.resolve(
typeof onfinally === 'function' ? onfinally() : onfinally
).then(() => {
throw reason
})
)
}
}
Promise.allSettled
ES2020
提出
Promise.allSettled(iterable)
方法接收一个iterable
对象,返回一个 Promise 实例,该实例的状态总是fulfilled
或pending
。在 iterable
参数内所有的 thenable
状态不论为fullfilled
还是rejected
,reslove
或reject
的值都会被包装成一个对象保留,当所有的thenable
执行完毕后该 Promise 实例会reslove
一个包含了这些所有对象的数组。如果有thenable
的状态为pending
,此实例的状态也为pending
。
具体用法:
const promise1 = new Promise((resolve) => {
resolve(1)
})
const promise2 = new Promise((resolve) => {
resolve(2)
})
const promise3 = new Promise((resolve, reject) => {
reject(3)
})
Promise.allSettled([promise1, promise2, promise3]).then(console.log)
/*
打印结果为:
[
{ status: 'fulfilled', value: 1 },
{ status: 'fulfilled', value: 2 },
{ status: 'rejected', reason: 3 }
]
*/
接口定义:
interface PromiseConstructor {
/**
* Creates a Promise that is resolved with an array of results when all
* of the provided Promises resolve or reject.
* @param values An array of Promises.
* @returns A new Promise.
*/
allSettledextends readonly unknown[] | readonly [unknown]>(values: T):
Promise<{ -readonly [P in keyof T]: PromiseSettledResultextends PromiseLike ? U : T[P]> }>;
/**
* Creates a Promise that is resolved with an array of results when all
* of the provided Promises resolve or reject.
* @param values An array of Promises.
* @returns A new Promise.
*/
allSettled(values: Iterable): Promiseextends PromiseLike ? U : T>[]>;
}
代码实现:
class MyPromise {
static allSettledextends readonly unknown[] | readonly [unknown]>(
values: T
): MyPromise<
{
-readonly [P in keyof T]: PromiseSettledResult<
T[P] extends PromiseLike ? U : T[P]
>
}
>
static allSettled(
values: Iterable
): MyPromiseextends PromiseLike ? U : T>[]>
// 重载函数的返回值有冲突,想不报错需要使用联合类型,这边图省事直接用 any 了
static allSettled(values: Iterable): MyPromise<any> {
// 大体写法参照 Promise.all()
return new MyPromise((reslove) => {
const resultArr: any[] = []
const doneArr: boolean[] = []
// 获取迭代器
const iter = values[Symbol.iterator]()
// 当前值
let cur = iter.next()
const resolveResult = (value: any, index: number, done?: boolean) => {
resultArr[index] = {
status: Status.FULFILLED,
value
}
doneArr[index] = true
if (done && doneArr.every((item) => item)) {
reslove(resultArr)
}
}
for (let i = 0; !cur.done; i++) {
const value = cur.value
doneArr.push(false)
cur = iter.next()
if (isPromise(value)) {
value.then(
(value) => {
resolveResult(value, i, cur.done)
},
(reason) => {
// 这里和 resolve 基本也没什么区别,修改一下状态和属性就ok了
resultArr[i] = {
status: Status.REJECTED,
reason
}
doneArr[i] = true
if (cur.done && doneArr.every((item) => item)) {
reslove(resultArr)
}
}
)
// 不是 thenable 直接存储
} else {
resolveResult(value, i, cur.done)
}
}
})
}
}
Promise.any
ESNEXT
提出,还处于实验版本,只有少部分浏览器支持
Promise.any(iterable)
方法接收一个 iterable
对象,返回一个 Promise 实例。只要iterable
中的一个 thenable
的状态为fulfilled
,就返回那个thenable
所reslove
的值,并且该实例的状态也为fulfilled
。如果iterable
中全为thenable
并且状态全部为rejected
,该实例的状态也为rejected
并且reject
一个AggregateError
类型的实例(它是 Error
的一个子类,用于把单一的错误集合在一起,目前还在实验阶段,只有少部分浏览器支持)。本质上,这个方法和Promise.all()
是相反的。
具体用法:
const pErr = new Promise((resolve, reject) => {
reject("总是失败");
});
const pSlow = new Promise((resolve, reject) => {
setTimeout(resolve, 500, "最终完成");
});
const pFast = new Promise((resolve, reject) => {
setTimeout(resolve, 100, "很快完成");
});
Promise.any([pErr, pSlow, pFast]).then((value) => {
console.log(value);
// pFast fulfils first
})
// 打印结果为: "很快完成"
接口定义:
interface PromiseConstructor {
/**
* The any function returns a promise that is fulfilled by the first given promise to be fulfilled, or rejected with an AggregateError containing an array of rejection reasons if all of the given promises are rejected. It resolves all elements of the passed iterable to promises as it runs this algorithm.
* @param values An array or iterable of Promises.
* @returns A new Promise.
*/
any(values: (T | PromiseLike)[] | Iterable>): Promise
}
代码实现:
class MyPromise {
static any(
values: (T | PromiseLike)[] | Iterable>
): MyPromise {
return new MyPromise((resolve, reject) => {
// 接收迭代器
const iter = values[Symbol.iterator]()
let cur = iter.next()
const doneArr: boolean[] = []
for (let i = 0; !cur.done; i++) {
const value = cur.value
cur = iter.next()
doneArr.push(false)
if (isPromise(value)) {
// 如果为 thenable,根据该 thenable 的状态进行判断
value.then(resolve, () => {
doneArr[i] = true
// 只有传入迭代器的值全是 thenable 并且 thenable 的状态全部为 rejected 才会触发
if (cur.done && doneArr.every((item) => item)) {
// 应该抛出 AggregateError 的错误类型,但是因为 AggregateError 因为是实验版本,所有只有最新版浏览器才会有,我这里就用 Error 代替了
const e = new Error('All promises were rejected')
e.stack = ''
reject(e)
}
})
} else {
resolve(value)
}
}
})
}
}
总结
本文使用 typescript,根据类型定义从零开始实现了一个 Promise,其中重点深入了对于then
方法的处理。虽不完美,但也算达到了想要的效果。作者技术有限,如果有什么错误或遗漏的地方还请在评论区中指出,顺便求个👍。
文章的代码已上传至github:
https://github.com/Col0ring/learning-es6/tree/main/Promise