一. 实现同步的promise
首先先来写一个🌰,一个同步执行的Promise
,执行完Promise
函数中的内容后,执行其.then
中的逻辑:
let p = new Promise((resolve, reject) => {
resolve('小黄瓜')
}).then((val)=>{
console.log(val, 'val'); // 小黄瓜
}, () => {})
根据上面例子得执行结果,我们可以得出一个很简单的结论:Promise是一个构造函数(类),接受一个匿名函数,该函数接受两个参数,分别是resolve
函数和reject
函数,分别代表变更Promise
状态为"成功"和"失败"。
当函数执行完毕后,会调用Promise
的实例方法then
,将执行结果传递给then
方法,然后执行then
方法中的逻辑。
那么先实现一个Promise
类:
export default class TinyPromise<T = any> {
// 当前promise状态
private _status = 'pedding'
// 当前执行状态接收的值,用于传递给then函数
private _value: any
constructor(executor: ExecutorType<T>) {
// 使用try进行错误捕获,如果执行失败直接调用_reject返回错误
try {
// 调用用户传递的函数,将_resolve和_reject作为参数传递
executor(this._resolve.bind(this), this._reject.bind(this))
} catch (error) {
this._reject(error)
}
}
private _resolve: Resolve<T> = (resolveValue) {
// 变更状态为成功
this._status = 'fulfilled'
// 保存执行结果
this._value = resolveValue
}
private _reject: Reject = (error) {
// 变更状态为失败
this._status = 'rejected'
this._value = error
}
}
在这个Promise
类中首先定义了一个泛型T,在用户传递的executor
函数将其约束为ExecutorType
类型,它的实现是这样的:
type ExecutorType<T> = (resolve: Resolve<T>, reject: Reject) => void
因为我们是直接将_resolve
和_reject
这两个实例方法传入到executor
函数中,所以Resolve
和Reject
类型当然也可以对_resolve
和_reject
这两个实例方法进行约束:
// _resolve
type Resolve<T = any> = (resolveValue: T | PromiseLike<T>) => void
// _reject
type Reject = (rejectValue: any) => void
为什么_resolve
的参数被约束为了T | PromiseLike<T>
? PromiseLike
是什么?PromiseLike
其实是ts
内置的一个类型,它的实现是这样的:
interface PromiseLike<T> {
/**
* 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<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): PromiseLike<TResult1 | TResult2>;
}
其实就是定义了一个包含有then方法,接受onfulfilled
和onrejected
函数的接口,可以理解为是一个"Promise"类型,相当于使用它来代表Promise
进行约束。
我们的_resolve
方法未来可以接受一个Promise
作为参数,所以将参数的约束定义为T | PromiseLike<T>
。
然后将_resolve
和_reject
函数的参数保存到_value
中,在未来调用then
方法的时候使用。
这里还有一个优化的点,我们可以将fulfilled
、rejected
、pedding
三种状态定义为枚举值,便于维护:
enum PROMISESTATUS {
PENDING = 'pending',
FULFILLED = 'fulfilled',
REJECTED = 'rejected'
}
相应的,我们的代码也可以改造成这样:
export default class TinyPromise<T = any> {
// 初始状态为pedding
private _status = PROMISESTATUS.PENDING
private _value: any
constructor(executor: ExecutorType<T>) {
try {
executor(this._resolve.bind(this), this._reject.bind(this))
} catch (error) {
this._reject(error)
}
}
private _resolve: Resolve<T> = (resolveValue) {
this._status = PROMISESTATUS.FULFILLED
this._value = resolveValue
}
private _reject: Reject = (error) {
this._status = PROMISESTATUS.REJECTED
this._value = error
}
}
接下来就可以实现then
方法了,其实then
方法的作用很简单,包括以后实现的更复杂的实现。它的核心作用只有一个,就是作为executor
函数执行后的回调函数。
then
方法接收两个函数作为参数,其中第一个函数代表执行成功状态的回调,第二个函数代表执行失败状态的回调。
// 暂时将resolveInThen与rejectinThen定义为any
public then(resolveInThen: any, rejectinThen: any) {
const {
_value,
_status
} = this
if(_status === PROMISESTATUS.FULFILLED) {
resolveInThen(_value)
}
if(_status === PROMISESTATUS.REJECTED) {
rejectinThen(_value)
}
}
这就实现了一个最基本的同步执行的Promise
来试一下上面的🌰:
let p = new TinyPromise((resolve, reject) => {
resolve('小黄瓜')
}).then((val)=>{
console.log(val, 'val'); // 小黄瓜
}, () => {})
二. 实现异步的Promise & 链式调用
其实上面实现的同步功能没有什么作用,因为Promise
本来就是处理异步,如果只能执行同步代码,那还有什么用😂。
let p = new TinyPromise((resolve) => {
setTimeout(() => {
resolve('小黄瓜')
}, 1000);
}).then((val)=>{
console.log(val, '第一个then');
return '小南瓜'
}, () => {}).then((val)=>{
console.log(val, '第二个then');
}, () => {})
上面这个🌰我们想要实现的效果是1秒钟后执行resolve
函数,传入’小黄瓜’,然后执行第一个then
函数,将执行器的参数进行传递,最后执行第二个then
函数,把第一个then
函数的返回值当作参数传入。
如果按照上一节实现的功能,肯定是满足不了目前的需求,首先按照js的执行机制,我们在执行器内定义了一个定时器,当js代码遇到异步任务时,会暂时挂起,先执行同步代码,所以在这个例子中,会先执行then
函数的代码,而此时Promise
的状态依然是pedding
,因为执行器定义了一个定时器,所以在1秒钟后Promise
的状态才被变更为fulfilled
。then
函数中的代码无法被正确执行。
其实解决的思路也很简单,我们如果能让then
函数内接收的函数执行晚于执行器内的异步代码就可以了!如果执行then
方法时,Promise
的状态还是处于pedding
状态,那么此时在执行器内的逻辑就肯定是异步代码!然后就将then
方法传入的函数先收集,再执行!先将回调函数收集起来,等异步操作执行完毕后,再执行回调函数。
那么链式调用怎么实现呢,其实直接返回一个Promise
就可以了,返回一个Promise
实例,当然可以在后面继续调用then
方法。
对TinyPromise
进行改造:
export default class TinyPromise<T = any> {
private _status = PROMISESTATUS.PENDING
private _value: any
// 成功回调函数队列
private _fulfilledQueues: Function[] = []
// 失败回调函数队列
private _rejectedQueues: Function[] = []
constructor(executor: ExecutorType<T>) {
try {
executor(this._resolve.bind(this), this._reject.bind(this))
} catch (error) {
this._reject(error)
}
}
_resolve(resolveValue: any) {
if(this._status !== PROMISESTATUS.PENDING) return
const runFulfilled = (value: any) => {
let cb;
// 取出每个函数执行
while(cb = this._fulfilledQueues.shift()) {
cb(value)
}
}
// 更改状态 保存参数 执行回调函数
this._value = resolveValue
this._status = PROMISESTATUS.FULFILLED
runFulfilled(resolveValue)
}
_reject(error: any) {
this._status = PROMISESTATUS.REJECTED
this._value = error
let cb;
while (cb = this._rejectedQueues.shift()) {
cb(error)
}
}
then(resolveInThen: any, rejectinThen: any) {
const {
_value,
_status
} = this
// 返回Promise实例
return new TinyPromise((resolveNext, rejectNext) => {
// 封装执行成功的操作
const fulfilled = (val: any) => {
let res = resolveInThen(val);
// 向后传递执行结果
resolveNext(res)
}
// 封装执行失败的操作
const rejected = (error: any) => {
let res = rejectinThen(error);
// 向后传递执行结果
rejectNext(res)
}
// 判断当前Promise状态:pedding:保存 fulfilled/rejected:直接执行
switch(_status) {
case PROMISESTATUS.PENDING:
this._fulfilledQueues.push(fulfilled)
this._rejectedQueues.push(rejected)
break
case PROMISESTATUS.FULFILLED:
fulfilled(_value)
break
case PROMISESTATUS.REJECTED:
rejected(_value)
break
}
})
}
}
我们定义了两个队列:_fulfilledQueues
和_rejectedQueues
,分别用于保存成功和失败的回调函数,在异步操作执行完毕后,取出两个队列中的函数进行执行。
执行上文中的🌰:
小黄瓜 第一个异步函数
小南瓜 第二个异步函数
三. 实现多级多异步
接下来实现在then
方法中再次定义异步方法,此时应该将后续的then
作为异步then
函数的回调函数来调用。
let p = new TinyPromise((res) => {
setTimeout(() => {
res('第一个异步Promise')
}, 1000)
}).then((val: any)=>{
console.log(val, '第一个then');
// 再次定义异步任务
return new TinyPromise((res, rej) => {
setTimeout(() => {
res('第二个异步Promise')
}, 1000)
})
}, () => {}).then((val: any)=>{
// 此时的then应当作为上一个异步的then
console.log(val, '第二个then');
}, () => {})
在增加了多异步的逻辑之后要在then
方法中定义执行函数时,对各种情况进行判断:
- 如果参数为非函数,则直接向后传递
- 如果参数为函数,执行函数,并保存执行结果向后传递
- 如果参数为Promise,调用then
接下来就实现改造后的then
方法:
then(resolveInThen: any, rejectinThen: any) {
const {
_value,
_status
} = this
return new TinyPromise((resolveNext, rejectNext) => {
const fulfilled = (val: any) => {
try {
// 非函数,直接向后传递
if (!isFunction(resolveInThen)) {
resolveNext(val)
} else {
// 执行函数
let res = resolveInThen(val);
// Promise调用then
if (isPromise(res)) {
res.then(resolveNext, rejectNext)
} else {
// 返回结果
resolveNext(res)
}
}
} catch (error) {
rejectNext(error)
}
}
// 失败状态的处理逻辑类似
const rejected = (error: any) => {
try {
if (!isFunction(rejectinThen)) {
rejectNext(error)
} else {
let res = rejectinThen(error);
if (isPromise(res)) {
res.then(resolveNext, rejectNext)
} else {
resolveNext(res)
}
}
} catch (error) {
rejectNext(error)
}
}
switch(_status) {
case PROMISESTATUS.PENDING:
this._fulfilledQueues.push(fulfilled)
this._rejectedQueues.push(rejected)
break
case PROMISESTATUS.FULFILLED:
fulfilled(_value)
break
case PROMISESTATUS.REJECTED:
rejected(_value)
break
}
})
}
判断是否为函数,使用ts中的自定义类型收窄:
export const isFunction = (val: unknown): val is Function =>
typeof val === 'function'
ts中使用is语法进行类型收窄,如果返回结果为true,那么此分支会被推断为Function
,判断Promise
类型类似:
export const isPromise = <T = any>(val: any): val is TinyPromise<T> => {
return val instanceof TinyPromise
}
至此,then
方法的功能已经基本上实现了,而对于ts的类型定义还是非常简陋,可以借鉴PromiseLink
来对我们的then
方法进行更精确的约束:
// then方法的返回值为Promise
public then<TRes1 = T, TRes2 = never>(resolveInThen?: onFulfilled<T, TRes1>, rejectinThen?: onRejected<TRes2>):
TinyPromise<TRes1 | TRes2> {}
// then方法的参数可以为空
// 如果不为空,则返回值可以为Promise
type onFulfilled<T, TRes1> = ((value: T) => TRes1 | PromiseLike<TRes1>) | undefined | null
type onRejected<TRes2> = ((err: any) => TRes2 | PromiseLike<TRes2>) | undefined | null
然后就可以执行一下本节开头的例子:
第一个异步Promise 第一个then
第二个异步Promise 第二个then
由于我们已经实现了then
方法之间的参数传递,所以也实现了穿透的功能:
new TinyPromise((reslove) => {
reslove('hello')
})
.then()
.then()
.then()
.then((res) => {
console.log(res) // 'hello'
})
四. 执行器传入Promise
问题又又又来了,如果想在执行器中传入Promise
呢?就想这样:
let p = new TinyPromise((res) => {
setTimeout(() => {
let t = new TinyPromise((res2, rej2) => {
res2('异步1')
})
// 在resolve中传入一个promise
res(t)
}, 1000)
}).then((val:any) => {
console.log(val, '1');
return new TinyPromise((res, rej) => {
setTimeout(() => {
res('异步2')
}, 1000)
})
}, () => { }).then((val: any) => {
console.log(val, '2');
}, () => { })
此时我们希望在第一个定时器执行完毕后调用res
传入Promise
,然后将后续的then
方法都挂载到这个Promise
上。
此时就需要修改_resolve
方法了:
private _resolve: Resolve<T> = (resolveValue) => {
const run = () => {
if (this._status !== PROMISESTATUS.PENDING) return
const runFulfilled = (value: any) => {
let cb;
while (cb = this._fulfilledQueues.shift()) {
cb(value)
}
}
const runRejected = (error: any) => {
let cb
while (cb = this._rejectedQueues.shift()) {
cb(error)
}
}
// 判断参数是否为Promise?
if (isPromise(resolveValue)) {
// 定义then方法,手动变更状态
resolveValue.then((val) => {
this._value = val
this._status = PROMISESTATUS.FULFILLED
runFulfilled(val)
}, (err) => {
this._value = err
this._status = PROMISESTATUS.REJECTED
runRejected(err)
})
} else {
// 不为Promise,直接变更状态,执行回调函数
this._value = resolveValue
this._status = PROMISESTATUS.FULFILLED
runFulfilled(resolveValue)
}
}
// 直接将resolve函数变为异步
setTimeout(run, 0);
}
在_resolve
函数中判断函数是否为Promise
?如果是的话,就手动指定then
函数,变更状态。
_reject
函数类似,只不过不需要处理这种情况了:
private _reject: Reject = (error) => {
if (this._status !== PROMISESTATUS.PENDING) return
const run = () => {
this._status = PROMISESTATUS.REJECTED
this._value = error
let cb;
while (cb = this._rejectedQueues.shift()) {
cb(error)
}
}
setTimeout(run, 0)
}
此时执行本节的例子:
异步1 1
异步2 2
五. 其他函数
catch
catch
函数只要用于捕获错误,而本身又可以继续调用then
函数,所以直接使用then
函数来传递一个错误即可:
catch<TRes>(onRejected: onRejected<TRes>): TinyPromise<T | TRes> {
return this.then(null, onRejected)
}
resolve
resolve
函数是一个静态方法,通过Promise.resolve
来调用,直接返回一个成功状态的Promise
,如果参数不是Promise
,也不自动进行转换:
static resolve<T>(value: T | PromiseLike<T>): TinyPromise<T> {
// 判断是否为Promise
if (isPromise(value)) {
return value;
}
return new TinyPromise(resolve => {
resolve(value);
});
}
reject
reject
函数返回一个失败状态的Promise
static reject<T = never>(error: any): TinyPromise<T> {
return new TinyPromise((resolve, reject) => reject(error))
}
all
Promise.all()
方法用于将多个 Promise 实例,包装成一个新的 Promise
实例。
const p = Promise.all([p1, p2, p3]);
上面代码中,Promise.all()
方法接受一个数组作为参数,p1、p2、p3都是 Promise
实例,如果不是,就会先调用下面讲到的Promise.resolve
方法,将参数转为 Promise
实例,再进一步处理。另外,Promise.all()
方法的参数可以不是数组,但必须具有 Iterator
接口,且返回的每个成员都是 Promise
实例。
p的状态由p1、p2、p3决定,分成两种情况。
(1)只有p1、p2、p3的状态都变成fulfilled
,p的状态才会变成fulfilled
,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被rejected
,p的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给p的回调函数。
static all<T>(list: T[]): TinyPromise<T[]> {
let values: any[] = []
// 计数
let count = 0
// 返回一个Promise,便于后续使用then方法
return new TinyPromise((resolve, reject) => {
for (let [k, v] of list.entries()) {
// 此时的this为Promise自身
this.resolve(v).then((res: any) => {
// 使用下标,保证顺序
values[k] = res
count++
// 遍历完成,全部then执行完毕后,调用then方法返回
if (count === list.length) return resolve(values)
}, (err: any) => {
reject(err)
})
}
})
}
遍历数组,因为我们直接调用了Promise.resolve
方法,在内部会直接将非Promise
也会直接进行转换,为每个遍历值创建一个成功状态的Promise
,并绑定then
方法,当所有的值都被遍历完成后,then
方法执行完毕后,count
这个计数的值也会与数组的长度相等,此时返回结果数组。
完整代码
type Resolve<T = any> = (resolveValue: T | PromiseLike<T>) => void
type Reject = (rejectValue: any) => void
type ExecutorType<T> = (resolve: Resolve<T>, reject: Reject) => void
// 非函数 / 返回值/promise
type onFulfilled<T, TRes1> = ((value: T) => TRes1 | PromiseLike<TRes1>) | undefined | null
type onRejected<TRes2> = ((err: any) => TRes2 | PromiseLike<TRes2>) | undefined | null
enum PROMISESTATUS {
PENDING = 'pending',
FULFILLED = 'fulfilled',
REJECTED = 'rejected'
}
export const isFunction = (val: unknown): val is Function =>
typeof val === 'function'
export const isPromise = <T = any>(val: any): val is TinyPromise<T> => {
return val instanceof TinyPromise
}
export default class TinyPromise<T = any> {
public _status: PROMISESTATUS = PROMISESTATUS.PENDING
public _value!: T | PromiseLike<T>
private _fulfilledQueues: Resolve[] = []
private _rejectedQueues: Reject[] = []
constructor(executor: ExecutorType<T>) {
try {
executor(this._resolve.bind(this), this._reject.bind(this))
} catch (error) {
this._reject(error)
}
}
private _resolve: Resolve<T> = (resolveValue) => {
const run = () => {
if (this._status !== PROMISESTATUS.PENDING) return
const runFulfilled = (value: any) => {
let cb;
while (cb = this._fulfilledQueues.shift()) {
cb(value)
}
}
const runRejected = (error: any) => {
let cb
while (cb = this._rejectedQueues.shift()) {
cb(error)
}
}
if (isPromise(resolveValue)) {
resolveValue.then((val) => {
this._value = val
this._status = PROMISESTATUS.FULFILLED
runFulfilled(val)
}, (err) => {
this._value = err
this._status = PROMISESTATUS.REJECTED
runRejected(err)
})
} else {
this._value = resolveValue
this._status = PROMISESTATUS.FULFILLED
runFulfilled(resolveValue)
}
}
setTimeout(run, 0);
}
private _reject: Reject = (error) => {
if (this._status !== PROMISESTATUS.PENDING) return
const run = () => {
this._status = PROMISESTATUS.REJECTED
this._value = error
let cb;
while (cb = this._rejectedQueues.shift()) {
cb(error)
}
}
setTimeout(run, 0)
}
public then<TRes1 = T, TRes2 = never>(resolveInThen?: onFulfilled<T, TRes1>, rejectinThen?: onRejected<TRes2>):
TinyPromise<TRes1 | TRes2> {
const {
_value,
_status
} = this
return new TinyPromise((resolveNext, rejectNext) => {
const fulfilled = (val: any) => {
try {
if (!isFunction(resolveInThen)) {
resolveNext(val)
} else {
let res = resolveInThen(val);
if (isPromise(res)) {
res.then(resolveNext, rejectNext)
} else {
resolveNext(res)
}
}
} catch (error) {
rejectNext(error)
}
}
const rejected = (error: any) => {
try {
if (!isFunction(rejectinThen)) {
rejectNext(error)
} else {
let res = rejectinThen(error);
if (isPromise(res)) {
res.then(resolveNext, rejectNext)
} else {
resolveNext(res)
}
}
} catch (error) {
rejectNext(error)
}
}
switch (_status) {
case PROMISESTATUS.PENDING:
this._fulfilledQueues.push(fulfilled)
this._rejectedQueues.push(rejected)
break
case PROMISESTATUS.FULFILLED:
fulfilled(_value)
break
case PROMISESTATUS.REJECTED:
rejected(_value)
break
}
})
}
catch<TRes>(onRejected: onRejected<TRes>): TinyPromise<T | TRes> {
return this.then(null, onRejected)
}
static resolve<T>(value: T | PromiseLike<T>): TinyPromise<T> {
if (isPromise(value)) {
return value;
}
return new TinyPromise(resolve => {
resolve(value);
});
}
static reject<T = never>(error: any): TinyPromise<T> {
return new TinyPromise((resolve, reject) => reject(error))
}
static all<T>(list: T[]): TinyPromise<T[]> {
let values: any[] = []
let count = 0
return new TinyPromise((resolve, reject) => {
for (let [k, v] of list.entries()) {
this.resolve(v).then((res: any) => {
values[k] = res
count++
if (count === list.length) return resolve(values)
}, (err: any) => {
reject(err)
})
}
})
}
}