手写一个简单的Promise

首先将Promise运行中所需要的常量定义好,即promise的三种状态

1
2
3
const PENDING = 'pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'

定义整体结构

首先创建一个Promise类,在类的内部定义好类的构造器、Promise原型上的then、catch,Promise上的resolve、reject、all以及race

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Promise {
constructor(excutor) {}
/*
Promise.prototype.then函数
接受两个函数参数,返回一个新的promise对象
*/
then(onResolved, onRejected) {}
/*
Promise.prototype.catch函数
接受一个函数参数,返回一个新的promise对象
*/
catch(onRejected) {}
/*
Promise.resolve
接受一个参数,返回一个指定值的成功promise对象
*/
static resolve(value) {}
/*
Pomise.reject
接受一个参数,返回一个指定值的失败promise对象
*/
static reject(reason) {}
/*
Promise.all
接受一组promise对象,如果全部成功,返回成功的promise否则返回失败的promise
*/
static all(promises) {}
/*
Promise.race
接受一组promise对象,其返回的promise对象的状态由第一个完成的promise对象决定
*/
static race(promises) {}
}

构造函数的实现

需要注意的一点是:由于resolve/reject函数需要判断promise实例的状态,并且resolve/reject的调用是直接调用。其this的指向为window,无法进行判断,所以需要在函数体内将this存起来(或者使用箭头函数),形成闭包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
constructor(excutor) {
// 将this存放起来
const self = this
// promise对象的状态
self.status = PENDING;
// promise对象存储的数据
self.data = undefined;
// promise对象指定的回调(一个promise对象可以指定多个回调函数,所以用数组存储{onResol
self.callbacks = []
// 定义内部resolve函数
function resolve(value) {
// 由于状态只能改变一次,如果二次修改直接return
if (self.status != PENDING) {
// 由于这里resolve的调用是直接调用所以this是window,所以用存起来的self(形成
return
}
// 改变状态
self.status = RESOLVED
// 存储数据
self.data = value
// 如果有回调函数就异步调用onResolved函数
if (self.callbacks.length > 0) {
// 遍历所有函数
self.callbacks.forEach(callback => {
// 异步执行onResolved函数
setTimeout(() => {
callback.onResolved(value)
}, 0)
});
}
}
// 定义内部reject函数
function reject(reason) {
// 由于状态只能改变一次,如果二次修改直接return
if (self.status != PENDING) {
return
}
// 改变状态
self.status = REJECTED
// 存储数据
self.data = reason
// 如果有回调函数就异步调用onRejected函数
if (self.callbacks.length > 0) {
// 遍历所有函数
self.callbacks.forEach(callback => {
// 异步执行onRejected函数
setTimeout(() => {
callback.onRejected(reason)
})
});
}
}
// 立即执行同步回调excutor
try {
// 改变状态的第三种方式抛出异常,在这里捕获异常,然后将状态改为rejected
excutor(resolve, reject)
} catch (error) {
// 交给reject函数处理
reject(error)
}
}

这里我为Promise对象添加了三个属性,status、data以及callbacks分别用于存储Promise的状态、状态值以及.then的回调函数列表。

resolve和reject改变Promise的状态有两种情况,一种是Promise在改变状态时,.then的回调函数还没有指定;另一种是Promise改变状态时.then的回调函数已经指定了,所以根据情况的不同我们做出了if (self.callbacks.length > 0)的判断,如果回调函数已经制定就直接执行响应的回调。

promise.then()/catch()

在执行其他代码之前,需要先判断是否已经传入了回调函数,如果传递了就使用该回调函数,否则就直接将状态以及状态值传递到下一层链

1
2
3
4
// 情况一 如果onResolved没传函数,则需要将成功的值传下去
onResolved = typeof onResolved == 'function' ? onResolved : value => value
// 情况二 如果onRejected没传函数,则需要将失败的值传下去(用于异常传透)
onRejected = typeof onRejected == 'function' ? onRejected : reason => { throw reason }

这里比较重要的就是封装了一个函数用于改变返回的新promise的状态,函数的实现就是判断不同的返回值执行不同的操作。在pending中,如果直接将传进来的回调存储到callbacks中是无法改变返回的新promise状态的,所以我们需要在传入的函数外层在套一个函数,在该函数内根据回调函数的返回值改变promise的状态。

promise.then():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
then(onResolved, onRejected) {
/*
两种特殊情况 onResolved没传和onRejected没传
*/
// 情况一 如果onResolved没传函数,则需要将成功的值传下去
onResolved = typeof onResolved == 'function' ? onResolved : value
// 情况二 如果onRejected没传函数,则需要将失败的值传下去(用于异常传透)
onRejected = typeof onRejected == 'function' ? onRejected : reaso
// 存储当前的this值
const self = this
// 返回值为一个promise对象
return new Promise((resolve, reject) => {
/*
封装改变状态的函数,该函数复用率很高
*/
function handle(callback) {
// 捕获异常 如果有异常抛出 返回的新promise对象的状态直接改变为
try {
// 接受回调的返回值
let result = callback(self.data)
// 如果返回值为一个promise对象
if (result instanceof Promise) {
// 调用then函数判断promise对象的状态
result.then(
// 如果该promise的状态为成功,则返回的新promise
// value => { resolve(value) },
// 如果该promise的状态为失败,则返回的新promise
// reason => { reject(reason) }
// 以上的简写形式 这个和简写之前的实现功能相同,由
resolve, reject
)
} else {
// 返回的不是promise对象,则直接将状态改变为resolved
resolve(result)
}
} catch (error) {
// 改变状态
reject(error)
}
}
/*
判断各种情况,由于在指定回调函数的时候,promise对象的状态不一定
*/
// 情况一 在指定回调时状态还没有改变
if (self.status == PENDING) {
// 将回调函数放入promise的回调数组中
self.callbacks.push({
// 在回调的外层在套一层函数,为了在执行的时候可以通过这里的
onResolved(value) {
handle(onResolved)
}, onRejected(reason) {
handle(onRejected)
}
})
}
// 情况二 在指定回调时状态已经改变 并且状态为resolved
else if (self.status == RESOLVED) {
// 异步执行回调函数
setTimeout(() => {
handle(onResolved)
})
}
// 情况三 在指定回调时状态已经改变 并且状态为rejected
else {
// 异步执行回调函数
setTimeout(() => {
handle(onRejected)
})
}
})
}

promise.catch就相当于没有传递onResolved只传递onRejected的promise.then所以实现起来就是这样

1
2
3
catch(onRejected) {
return this.then(undefined, onRejected)
}

Promise.resolve()/reject()的实现

对于reject来说,他接受只接受失败的数据不接受其他的东西,而且返回的promise对象的状态是固定的。所以只需要返回一个值为固定的失败promise即可

1
2
3
4
static reject(reason) {
// 返回一个值为reason的失败的promise
return new Promise((resolve, reject) => { reject(reason) })
}

而对于resolve来说,他接受的函数有三种情况

  • 1.成功的值(返回成功的promise)
  • 2.成功的promise对象(返回成功的promise值为该对象的成功的值)
  • 3.失败的promise对象(返回失败的promise值为该对象失败的值)

所以我们需要对情况进行判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static resolve(value) {
/*
返回一个promise对象,但是有三种情况
值为value的成功promise
value为promise 返回值为value成功的值的成功promise
返回值为value失败的值的失败promise
*/
return new Promise((resolve, reject) => {
// 判断value的类型
if (value instanceof Promise) {
// 根据value的状态改变
value.then(resolve, reject)
} else {
// 改变状态
resolve(value)
}
})
}

Promise.all/race()的实现

Promise.all接受一组promise对象,其返回状态有两种

  • 1.所有promise为成功,则返回的promise对象的成功值为该组promise的成功值组成的数组
  • 2.有一个promise失败,返回的promise对象的失败者为第一个改变状态的失败的值

首先需要遍历promise数组,执行每一个.then函数判断它的状态

在失败的情况下直接将状态改为失败即可

1
2
3
4
reason => {
// 如果有一个状态为失败,则返回的promise为失败
reject(reason)
}

在成功的情况下,需要判断是否全部成功,所以需要一个计数器,每进入一次onRejected就加一,然后判断计数器和promises的长的是否相同,如果相同,就将promise的状态改为成功

1
2
3
4
5
6
7
8
9
10
value => {
// 加一次成功
resolvedCount++
// 将成功的值存储在数组中,对应的位置
values[index] = value
// 如果全部成功就改变新的promise状态
if (resolvedCount == promises.length) {
resolve(values)
}
},

Promise.race同样接受一组promise,其返回的promise对象的状态依据第一个执行完成的promise数组中的promise的状态与值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
return new Promise((resolve, reject) => {
// 遍历promises数组
promises.forEach((p, index) => {
// 执行每一个promise的then方法,判断其状态
p.then(
// 接受第一个执行完成的promise的状态与值
value => {
resolve(value)
},
reason => {
reject(reason)
}
)
})
})

完善:由于传入的promise数组中不一定全是promise对象,还有可能包含数组和字符串等其他类型的数据,所以我们需要在遍历p的时候在外面套上一层Promise.resolve(p)这样就算是传入的不是promise对象,那么在便利的时候也会变为promise对象,改变状态传递值

Promise.all():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
static all(promises) {
// 定义一个计数器,用来判断是否全部成功
let resolvedCount = 0
// 定义一个数组保存所有的成功的值
const values = new Array(promises.length)
// 返回一个新的promise对象
return new Promise((resolve, reject) => {
// 遍历promises数组
promises.forEach((p, index) => {
Promise.resolve(p).then(
value => {
// 加一次成功
resolvedCount++
// 将成功的值存储在数组中,对应的位置
values[index] = value
// 如果全部成功就改变新的promise状态
if (resolvedCount == promises.length) {
resolve(values)
}
},
reason => {
// 如果有一个状态为失败,则返回的promise为失败
reject(reason)
}
)
})
})
}

Promise.race():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static race(promises) {
// 返回一个新的promise对象
return new Promise((resolve, reject) => {
// 遍历promises数组
promises.forEach((p, index) => {
// 执行每一个promise的then方法,判断其状态
Promise.resolve(p).then(
// 接受第一个执行完成的promise的状态与值
value => {
resolve(value)
},
reason => {
reject(reason)
}
)
})
})
}

至此我们就写了一个可以实现大部分常用Promise方法的自定义Promise,但是我们所写的Promise回调都是通过宏队列的定时器模拟实现的,并没有实现原生Promise中的微队列。