《前端开发核心知识进阶》笔记-手写Promise

花了两周,对手写 Promise 篇章进行了总结,一步一步更着书籍中做,也更熟悉了 Promise。

callback到Promise

微信小程序的 callback 接口可以使用具象的 Promise 进行封装。

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
// wx的请求方法, callback 方式
wx.request({
url,
data,
method,
header: {
// 通用的header设置
},
success: function (res) {
console.log(res.data)
},
fail: function (res) {
console.error(res.data)
}
})

// promise 方法
const promiseify = fn => args =>
new Promise((resolve, reject) => {
args.success = function (res) {
return resolve(res)
}
args.fail = function (res) {
return reject(res)
}
})

const wxRequest = promiseify(wx.request)

Promise 初实现

一步一步完成,初步的 Promise ​

初见雏形

Promise 本质为一个构造函数,在 Promise/A+规范 称 Promise 的构造函数为 executor ,它是函数类型的参数。这个函数“自动”具有 resolve、reject 两个方法作为参数。 ​

进一步解释,Promise 构造函数返回一个 Promise 对象实例,这个返回的 Promise 对象具有一个 then 方法。在 then 的方法中,调用者可以定义两个参数,分别是 onfulfilled 和 onrejected,他们都是函数类型的参数。其中,onfulfilled 通过参数可以获取 Promise 对象经过 resolve 处理后的值,onrejected 可以获取 Promise 对象经过 reject 处理后的值。通过这个值,可以处理异步操作完成后的逻辑。 ​

1
2
function Promise (executor) {}
Promise.prototype.then = function (onfulfilled, onrejected) {}

在使用 new 关键字调用 Promise 构造函数时,在合适的时机(往往是异步操作结束时)调用 executor 的参数 ​

  1. resolve,并将经过 resolve 处理的值作为 resolve 的参数执行,这个值便可以在后续的 then 方法的第一个函数参数(onfulfilled)中拿到。
  2. reject,出现错误的情况,将错误信息作为 reject 的参数执行,这个错误信息可以在后续的 then 方法的第二个函数参数(onrejected)中执行。

综上,我们需要两个变量分别存储 resolve 和 reject 处理后的值(根据 Promise 的唯一性,使用一个变量也能存储);同时还需要存在一个状态,表示 Promise 实例的状态(pending、fulfilled、rejected);最后提供 resolve 和 reject 方法,这两个方法需要作为 executor 的参数提供给开发者使用。 ​

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Promise(executor) {
this.status = 'pending'
this.value = null
this.reason = null

const resolve = value => this.value = value

const reject = reason => this.reason = reason

executor(resolve, reject)
}

Promise.prototype.then = function(onfulfilled = Function.prototype, onrejected = Function.prototype) {
onfulfilled(this.value)
onrejected(this.reason)
}

then 放在 Promise 构造函数的原型上,不放在构造函数内部的原因? 涉及到了原型和原型链的知识,每个 Promise 实例的 then 方法逻辑都是一致的,实例在调用该方法的时候,可以通过原型(Promise.prototype)来调用,而不需要每次实例化都创建一个 then 方法,以便节省内存,显然更合适。

实现状态完善

要知道,一个 Promise 实例的状态只能从 pending 变成 fulfilled,或者从 pending 变成 rejected。状态一旦变更,就不能再次变化或者逆转。因此我们需要在 resolve 和 reject 添加状态的判断。 ​

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
function Promise(executor) {
this.status = 'pending'
this.value = null
this.reason = null

const resolve = value => {
if (this.status === 'pending') {
this.value = value
this.status = 'fulfilled'
}
}

const reject = reason => {
if (this.status === 'pending') {
this.reason = reason
this.status = 'rejected'
}
}

executor(resolve, reject)
}

Promise.prototype.then = function(onfulfilled, onrejected) {
onfufilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
onrejected = typeof onrejected === 'function' ? onrejected : error => { throw error }

if (this.status === 'fulfilled') {
onfulfilled(this.value)
}
if (this.status === 'rejected') {
onrejected(this.reason)
}
}

异步实现完善

这里完善的核心就是,在异步的情况下,也就是 status 为 'pending' 的时候,我们需要先收集对应的 onfulfilled 和 onrejected 方法,在异步结束后,在调用对应的任务。而且我们需要包装我们的 resolve 和 reject 函数,避免影响到外界的同步代码执行。保证 Promise 的决议异步执行。这里我们使用 setTimeout 实现,实际上,为了保证 Promise 为 microtasks,很多Promise的实现库用了 MutationObserver 来模拟 nextTick。 ​

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
function Promise(executor) {
this.status = 'pending'
this.value = null
this.reason = null
this.onFulfilledFunc = Function.prototype
this.onRejectedFunc = Function .prototype

const resolve = value => {
if (value instanceof Promise) {
return value.then(resolve, reject)
}

setTimeout(() => {
if (this.status === 'pending') {
this.value = value
this.status = 'fulfilled'

this.onFulfilledFunc(this.value)
}
})
}

const reject = reason => {
setTimeout(() => {
if (this.status === 'pending') {
this.reason = reason
this.status = 'rejected'

this.onRejectedFunc(this.reason)
}
})
}

executor(resolve, reject)
}

Promise.prototype.then = function(onfulfilled, onrejected) {
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
onrejected = typeof onrejected === 'function' ? onrejected : error => { throw error }

if (this.status === 'fulfilled') {
onfulfilled(this.value)
}

if (this.status === 'rejected') {
onrejected(this.reason)
}

if (this.status === 'pending') {
this.onFulfilledFunc = onfulfilled
this.onRejectedFunc = onrejected
}
}

细节完善

接下来我们需要实现的是,在实例变更之前添加多个 then 方法的收集工作。 ​

上面一步中如果我们多次调用 then 方法,第二次的 then 方法中的 onFulfilledFunc 会覆盖第一个 then 方法中的 onFulfilledFunc。所以,需要所有的 onFulfilledFunc 存储到一个数组 onFulfilledArray 中,在当前 Promise 被决议时依次执行 onFulfilledArray 数组内的方法就可以了。对于 onRejectedFunc 同理。 ​

第二个需要完善的细节部分是,在构造函数中如果出错,将会自动触发 Promise 实例状态变为 rejected,因此我们用 try...catch 对 executor 进行包裹。 ​

1
2
3
4
5
try {
executor(resolve, reject)
} catch(e) {
reject(e)
}

到这我们就初步实现了基本的 Promise。并且实现的过程中,完成了以下几条重要的结论。

  • Promise 的状态具有凝固性
  • Promise 可以在 then 方法第二个参数中进行错误处理
  • Promise 实例可以添加多个 then 处理场景

Promise 链式调用

链式调用

先了解一下链式调用是什么?下面两种情况,都能正确的完成链式调用。

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
const promise = new Promise((reslove, rejcet) => {
setTimeout(() => {
reslove('lucas')
}, 2000)
})

// 0. 返回普通值
// lucas , lucas next then
promise.then(data => {
console.log(data)
return `${data} next then`
})
.then(data => {
console.log(data)
})

// 1. 返回一个 Promise 实例
// lucas, (after 2s) lucas next then
promise.then(data => {
console.log(data)
return new Promise((resolve, reject) => {
resolve(`${data} nect then`)
}, 4000)
})
.then(data => {
console.log(data)
})

通过上例中,我们可以看到,一个 Promise 的 then 方法是支持再次返回一个 Promise 实例的,也支持返回一个非 Promise 实例的普通值,并且返回的这个 Promise 实例或这个非 Promise 实例的普通值将会传给下一个 then 方法的 onfulfilled 函数或 onrejected 函数,这样,then方法就支持链式调用了。

初步实现

首先,让我们先考虑普通值的情况。完善阶段再考虑将 Promise 实例传递。 ​ 作为简单值来说,首先想到的就是用一个新的 Promise 实例,即 Promise2 来包装简单值,并将 Promise2 返回。 ​

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
Promise.prototype.then = function(onfulfilled, onrejected) {
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
onrejected = typeof onrejected === 'function' ? onrejected : error => { throw error }

let promise2
if (this.status === 'fulfilled') {
return promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
// 这个新的 promise2 resolved 的值为 onfulfilled 的执行结果
let result = onfulfilled(this.value)
resolve(result)
} catch (e) {
reject(e)
}
})
})
}

if (this.status === 'rejected') {
onrejected(this.reason)
}

if (this.stauts === 'pending') {
this.onFulfilledArray.push(onfulfilled)
this.onRejectedArray.push(onrejected)
}
}

同理将 this.status === 'rejected' 状态和 this.status === 'pending' 状态也要加入相同的逻辑。需要重要注意的是,this.status === 'pending' 分支中,需要收集对应的,onFulfilledArray 或 onRejectedArray。在异步处理结束后,依次处理收集的函数。所以实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
if (this.status === 'pending') {
return promise2 = new Promise((resolve, reject) => {
this.onFulfilledArray.push(() => {
try {
let result = onfulfilled(this.value)
resolve(result)
} catch(e) {
reject(e)
}
})

this.onRejectedArray.push(() => {
try {
let result = onrejected(this.reason)
resolve(result)
} catch(e) {
reject(e)
}
})
})
}

完善实现

上一步,初步实现,已经实现了返回一个直接值情况,我们需要重构修改,let result = onfulfilled(this.value)let result = onrejected(this.reason) 代码逻辑。我们抽象一个 resolvePromise 来统一处理。

1
2
3
4
5
6
const resolvePromise = (promise2, result, resolve, reject) => {
// 处理返回的 promise 实例
}

// let result = onfulfilled(this.value)
resolvePromise(promise2, result, resolve, reject)

resolvePromise接收4个参数

  • promise2:返回的 Promise 实例。
  • result:onfulfilled 或 onrejected 函数的返回值。
  • resolve:promise2 的 resolve 方法。
  • reject:promise2 的 reject 方法。

需要注意的是,如果 promise 和 promise2 是同一个实例,需要抛出错误。详见,promise规范 2.3.1 章节

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
// promise 完整实现
function Promise(executor) {
this.status = 'pending'
this.value = null
this.reason = null
this.onFulfilledArray = []
this.onRejectedArray = []

const reslove = value => {
if (value instanceof Promise) {
return value.then(resolve, reject)
}
setTimeout(() => {
if (this.status === 'pending') {
this.value = value
this.status = 'fulfilled'

this.onFulfilledArray.forEach(func => {
func(value)
})
}
})
}

const reject = reason => {
setTimeout(() => {
if (this.status === 'pending') {
this.reason === reason
this.status = 'rejected'

this.onRejectedArray.forEach(func => {
func(reason)
})
}
})
}

try {
executor(resolve, reject)
} catch(e) {
reject(e)
}
}

const reslovePromise = (promise2, result, resolve, reject) => {
// 当 result 和 promise2 相等时,也就是 onfulfilled 返回 promise2 时,进行抛错
if (result === promise2) {
reject(new TypeError('error due to circular reference'))
}

// 是否已经执行过 onfulfilled 或 onrejected
let consumed = false
let thenable

if (result instanceof Promise) {
if (result.status === 'pending') {
result.then(function(data) {
resolvePromise(promise2, data, resolve, reject)
}, reject)
} else {
result.then(resolve, reject)
}
return
}

let isComplexResult = target => (typeof target === 'function' || typeof target === 'object') && (target !== null)

// 如果返回的是疑似 Promise 类型
if (isComplexResult(result)) {
try {
thenable = result.then
// 判断返回值是否是 Promise 类型
if (typeof thenable === 'function') {
thenable.call(result, function(data) {
if (consumed) {
return
}
consumed = true

return resolvePromise(promise2, data, resolve, reject)
}, function(error) {
if (consumed) {
return
}
consumed = true

return reject(error)
})
} else {
resolve(result)
}
} catch(e) {
if (consumed) {
return
}
consumed = true
return reject(true)
}
} else {
resolve(result)
}
}

Promise.prototype.then = function(onfulfilled, onrejected) {
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
onrejected = typeof onrejected === 'function' ? onrejected : error => { throw error }

// promise2 将作为 then 方法的返回值
let promise2

if (this.status === 'fulfilled') {
return promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
// 这个新的 promise2 的经过 resolve 处理后的值为 onfulfilled 的执行结果
let result = onfulfilled(this.value)
resolvePromise(promise2, result, resolve, reject)
} catch(e) {
reject(e)
}
})
})
}

if (this.status === 'rejected') {
return promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
// 这个新的 promise2 的经过 reject 处理后的值为 onrejected 的执行结果
let result = onrejected(this.reason)
resolvePromise(promise2, result, resolve, reject)
} catch(e) {
reject(e)
}
})
})
}

if (this.status = 'pending') {
return promise2 = new Promise((resolve, reject) => {
this.onFulfilledArray.push(value => {
try {
let result = onfulfilled(value)
resolvePromise(promise2, result, resolve, reject)
} catch(e) {
reject(e)
}
})

this.onRejectArray.push(reason => {
try {
let result = onrejected(reason)
resolvePromise(promise2, result, resolve, reject)
} catch(e) {
reject(e)
}
})
})
}
}

Promise 方法实现

看一段代码。

1
2
3
4
5
6
7
8
9
10
11
12
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('lucas')
})
})

promise
.then(null)
.then(data => {
console.log(data)
})
// 期望返回值 2s后输出 lucas

穿透实现

其含义为:给 then() 方法传递非函数值作为其参数时,实际上会被解析为 then(null),这时,上一个 Promise 对象的决议结果便会 "穿透" 到下一个 then 方法中。前面的写法中,已经给对应的函数加上了判断。 ​

1
2
3
4
5
Promise.prototype.then = function(onfulfilled = Function.prototype, onrejected = Function.prototype) {
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
onrejected = typeof onrejected === 'function' ? onrejected : error => { throw error }
// ..
}

静态方法和其他实现

接下来会讲解以下方法的实现

  • Promise.prototype.catch
  • Promise.resolve
  • Promise.reject
  • Promise.all
  • Promise.race

Promise.prototype.catch 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
reject('lucas error')
}, 2000)
})

promise.then(data => {
console.log(data)
}).catch(error => {
console.log(error)
})

// 以上代码会在 2s 后输出 lucas error
// 等价于:
Promise.prototype.catch = function(catchFunc) {
return this.then(null, catchFunc)
}

Promise.resolve 实现

MDN解释: Promise.resolve(value)方法返回一个以给定值解析后的 Promise 实例对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Promise.resolve('data').then(data => {
console.log(data)
})
console.log(1)

// 先输出 1, 再输出 data
// 实现方式:
Promise.resolve = function(value) {
return new Promise((resolve, reject) => {
resolve(value)
})
}
Promise.reject = function(value) {
return new Promise((resolve, reject) => {
reject(value)
})
}

Promise.all 实现

MDN解释:Promise.all(iterable) 方法返回一个 Promise 实例,此实例在 iterable 参数内的所有 Promise 实例都 resolved 或参数中不包含 Promise 实例时完成回调;如果参数中的 Promise 实例有一个 rejected,则此实例回调失败,失败原因时第一个 Promise 实例失败的原因。

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
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('lucas')
}, 2000)
})

const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('lucas')
}. 2000)
})

Promise.all([promise1, promise2]).then(data => {
console.log(data)
})

// 2s后返回,['lucas', 'lucas']
// 实现:
Promise.all = function(promiseArray) {
if (!Array.isArray(promiseArray)) {
throw new TypeError('The arguments should be an array')
}

return new Promise((resolve, reject) => {
try {
let resultArray = []

const length = promiseArray.length

for (let i = 0; i < length; i++) {
promiseArray[i].then(data => {
resultArray.push(data)

if (resultArray.length === length) {
resolve(resultArray)
}
}, reject)
}
} catch(e) {
reject(e)
}
})
}

Promise.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
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('lucas1')
}, 2000)
})

const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
reoslve('lucas2')
}, 4000)
})

Promise.race([promise1, promise2]).then(data => {
console.log(data)
})

// 2s 输出 lucas1
// 实现:
Promise.race = function(promiseArray) {
if (!Array.isArray(promiseArray)) {
throw new TypeError('The arguments should be an array!')
}
return new Promise((resolve, reject) => {
try {
const length = promiseArray.length
for (let i = 0; i < length; i++) {
promiseArray[i].then(resolve, reject)
}
} catch(e) {
reject(e)
}
})
}

引用

评论

加载中,最新评论有1分钟延迟...