手写实现Promise

Promise的出现

在 Promise 出现以前,在我们处理多个异步请求嵌套时,代码往往是个回掉地狱

1
2
3
4
5
6
7
8
9
let fs = require('fs')

fs.readFile('./name.txt','utf8',function(err,data){
fs.readFile(data, 'utf8',function(err,data){
fs.readFile(data,'utf8',function(err,data){
console.log(data);
})
})
})

为了拿到回调的结果,我们必须一层一层的嵌套,可以说是相当恶心了。而且基本上我们还要对每次请求的结果进行一系列的处理,使得代码变的更加难以阅读和难以维护,这就是传说中臭名昭著的回调地狱~产生回调地狱的原因归结起来有两点:

  1. 嵌套调用,第一个函数的输出往往是第二个函数的输入;
  2. 处理多个异步请求并发,开发时往往需要同步请求最终的结果。

原因分析出来后,那么问题的解决思路就很清晰了:

  1. 消灭嵌套调用:通过 Promise 的链式调用可以解决;
  2. 合并多个任务的请求结果:使用 Promise.all 获取合并多个任务的错误处理。

Promise 正是用一种更加友好的代码组织方式,解决了异步嵌套的问题。

我们来看看上面的例子用 Promise 实现是什么样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let fs = require('fs')

function read(filename) {
return new Promise((resolve, reject) => {
fs.readFile(filename, 'utf8', (err, data) => {
if (err) reject(err);
resolve(data);
})
})
}

read('./name.txt').then((data)=>{
return read(data)
}).then((data)=>{
return read(data)
}).then((data)=>{
console.log(data);
},err=>{
console.log(err);
})

在传统的异步编程中,如果异步之间存在依赖关系,就需要通过层层嵌套回调的方式满足这种依赖,如果嵌套层数过多,可读性和可以维护性都会变得很差,产生所谓的“回调地狱”,而 Promise 将嵌套调用改为链式调用,增加了可阅读性和可维护性。也就是说,Promise 解决的是异步编码风格的问题。 那 Promise 的业界实现都有哪些呢? 业界比较著名的实现 Promise 的类库有 bluebird、Q、ES6-Promise。

Promise/A+手写步骤分析

我们想要手写一个 Promise,就要遵循 Promise/A+ 规范,业界所有 Promise 的类库都遵循这个规范。

其实 Promise/A+ 规范对如何实现一个符合标准的 Promise 类库已经阐述的很详细了。每一行代码在 Promise/A+ 规范中都有迹可循,所以在下面的实现的过程中,我会尽可能的将代码和 Promise/A+ 规范一一对应起来。

中文译文:https://www.ituring.com.cn/article/66566

本文讲解使用ES5方式实现,代码仓库中补充ES6 class方式实现

结合Promise/A+的规范,边分析基础特征,边一步步实现代码:

promise 有三个状态:pendingfulfilled,or rejected

根据三种状态,我们先定义成枚举,方便后续使用

1
2
3
4
5
const State = {
PENDING: Symbol.for('pending'),
FULFILLED: Symbol.for('fulfilled'),
REJECTED: Symbol.for('rejected')
}

当处于某一种状态时又需要满足以下的条件:

  • 等待态(Pending)
    • 可以迁移至执行态或拒绝态
  • 执行态(Fulfilled)
    • 不能迁移至其他任何状态
    • 必须拥有一个不可变的终值
  • 拒绝态(Rejected)
    • 不能迁移至其他任何状态
    • 必须拥有一个不可变的据因

根据以上条件当改变状态时,抽象成一个通用改变State的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* this需要绑定指向Promise实例
* @param {State} state
*/
function changeState(state) {
if (this.state === state) {
throw new Error('不能迁移至相同状态')
}
if (state === State.PENDING) {
throw new Error('不允许迁移至Pending状态')
}
if (this.state === State.FULFILLED || this.state === State.REJECTED) {
throw new Error('当前State不为Pending状态时则不能再迁移至其他任何状态')
}
this.state = state
}

Promise 是一个拥有 then 方法的对象或函数,且new Promise()时,需要传递一个executor()函数执行器。

那么我们先定义两个工具函数,后续也会用到

1
2
3
4
5
6
7
8
9
// 检测是否是一个函数
function isFunction(check) {
return (typeof check === 'function' || Object.prototype.toString.call(check) === '[object Function]')
}

// 检测是否是一个对象
function isObject(check) {
return check === Object(check)
}

那么根据定义可以实现一下代码

1
2
3
4
5
6
7
8
9
/**
* Promise类
*/
function Promise(executor) {

if (isFunction(executor)) {
executor()
}
}

executor并接受两个参数,分别是fulfillreject,且该函数执行器立即执行。

并且这里要结合第一点:

Promise中有变量来代表当前的状态,且状态默认为Pending

Promise有一个value保存成功状态的值,可以是undefined/thenable/promise;「规范 Promise/A+ 1.3」

Promise 有一个reason保存失败状态的值;「规范 Promise/A+ 1.5」

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 调用方法时改变Promise状态且拥有一个终值
function fulfill(value) {
changeState.call(this, State.FULFILLED)
this.value = value
}

// 调用方法时改变Promise状态且拥有一个据因
function reject(reason) {
changeState.call(this, State.REJECTED)
this.reason = reason
}

/**
* Promise类
*/
function Promise(executor) {
this.state = State.PENDING
this.value = undefined
this.reason = undefined

if (isFunction(executor)) {
executor(fulfill.bind(this), reject.bind(this))
}
}

通常fulfill的定义也可以表示为resolve,但是本文和后续方法名字有点冲突不方便查看所以定义为fulfill,平常所用的resolve意义一样。

一个 promise 必须提供一个 then 方法以访问其当前值、终值和据因。且接受两个参数onFulfilled, onRejected

onFulfilledonRejected 都是可选参数,并且两者不为函数则都需要忽略(内部实现用默认函数替代)

如果 onFulfilled 是函数:

  • promise 执行结束后其必须被调用,其第一个参数为 promise 的终值
  • promise 执行结束前其不可被调用
  • 其调用次数不可超过一次

如果 onRejected 是函数:

  • promise 被拒绝执行后其必须被调用,其第一个参数为 promise 的据因
  • promise 被拒绝执行前其不可被调用
  • 其调用次数不可超过一次

onFulfilledonRejected 只有在执行环境堆栈仅包含平台代码时才可被调用(后面再解释)

根据以上定义,完善现在的代码

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
// 增加try catch保证稳定执行,因为changeState在不正当切换State时会抛出错误
function fulfill(value) {
try {
changeState.call(this, State.FULFILLED)
this.value = value
// 当状态改变时,需要遍历执行队列中的任务
this.fulfillQueue.forEach(function (fn) { fn() })
} catch { }
}

function reject(reason) {
try {
changeState.call(this, State.REJECTED)
this.reason = reason
// 当状态改变时,需要遍历执行队列中的任务
this.rejectQueue.forEach(function (fn) { fn() })
} catch { }
}

/**
* Promise类
*/
function Promise(executor) {
this.state = State.PENDING
this.value = undefined
this.reason = undefined

// 保存异步执行的队列 { fulfill, reject }
this.fulfillQueue = []
this.rejectQueue = []

if (isFunction(executor)) {
executor(fulfill.bind(this), reject.bind(this))
}
}

Promise.prototype.then = function(onFulfilled, onRejected) {
const _self = this
if (this.state === State.FULFILLED) {
onFulfilled(this.value)
} else if (this.state === State.REJECTED) {
onRejected(this.reason)
} else {
// 当还没执行时不可以被调用,那么需要将此保存起来,在后续状态改变后调用
this.fulfillQueue.push(function() {
onFulfilled(_self.value)
})

this.rejectQueue.push(function() {
onRejected(_self.reason)
})
}
}

then 方法必须返回一个 promise 对象

Promise的优势就在于链式调用,在我们使用Promise的时候,当then函数中返回了一个值,不管是什么值,我们都能在下一个then中获取到,这就是所谓的then的链式调用。而且,当我们不再then中放入参数,例如:promise.then().then(),那么其后面的then依然可以得到之前then返回的值,这就是值得穿透。这也是规范实现得思路。

1
const promise2 = promise1.then(onFulfilled, onRejected);
  • 如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程[[Resolve]](promise2, x)
  • 如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e
  • 如果 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值
  • 如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的据因

理解上面的“返回”部分非常重要,即:不论 promise1reject 还是被 resolvepromise2 都会被 resolve,只有出现异常时才会被 rejected

分析一下上述可以看到,then方法需要返回一个新的Promise对象(promise2),且需要进行处理promise1中then两个参数的返回值

并且多了一个resolve方法定义Promise解决过程,方法的参数传入(promise2,x)。具体下面再实现,先进行调用。

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
/**
* Promise解决过程
* @param {Promise} promise2
* @param {value} x
*/
function resolve(promise2, x) {}

/**
* Promise类
*/
function Promise(executor) {
this.state = State.PENDING
this.value = undefined
this.reason = undefined

// 保存异步执行的队列 { fulfill, reject }
this.fulfillQueue = []
this.rejectQueue = []

if (isFunction(executor)) {
executor(fulfill.bind(this), reject.bind(this))
}
}

Promise.prototype.then = function(onFulfilled, onRejected) {
const _self = this // 处理this指向
// 如果 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值
onFulfilled = isFunction(onFulfilled) ? onFulfilled : function(v) { return v }
// 如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的据因
onRejected = isFunction(onRejected) ? onRejected : function(err) { throw err }

const promise2 = new Promise(function () {
if (_self.state === State.FULFILLED) {
try {
// 如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程
const x = onFulfilled(_self.value)
resolve(promise2, x)
} catch (e) {
// 如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e
reject.call(promise2, e)
}
} else if (_self.state === State.REJECTED) {
try {
// 如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程
const x = onRejected(_self.reason)
resolve(promise2, x)
} catch (e) {
// 如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e
reject.call(promise2, e)
}
} else {
// 当还没执行时不可以被调用,那么需要将此保存起来,在后续状态改变后调用
_self.fulfillQueue.push(function() {
onFulfilled(_self.value)
})

_self.rejectQueue.push(function() {
onRejected(_self.reason)
})
}
})

return promise2
}

Promise解决过程 (The Promise Resolution Procedure)

我们先看看规范是怎么定义的:

Promise 解决过程是一个抽象的操作,其需输入一个 promise 和一个值,我们表示为[[Resolve]](promise, x),如果 x 有 then 方法且看上去像一个 Promise ,解决程序即尝试使 promise 接受 x 的状态;否则其用 x 的值来执行 promise 。

这种thenable 的特性使得 Promise 的实现更具有通用性:只要其暴露出一个遵循 Promise/A+ 协议的 then 方法即可;这同时也使遵循 Promise/A+ 规范的实现可以与那些不太规范但可用的实现能良好共存。

运行 [[Resolve]](promise, x)需遵循以下步骤:

  • x 与 promise 相等
    如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise

  • x 为 Promise时 ,则使 promise 接受 x 的状态 :

    • 如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝

    • 如果 x 处于执行态,用相同的值执行 promise

    • 如果 x 处于拒绝态,用相同的据因拒绝 promise

  • x 为对象或函数时

    • x.then 赋值给 then

    • 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise

    • 如果 then 是函数,将 x 作为函数的作用域this 调用之。传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise:
      (1) 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
      (2) 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
      (3) 如果 resolvePromiserejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
      (4) 如果调用 then 方法抛出了异常 e, 如果resolvePromiserejectPromise 已经被调用,则忽略之。否则以 e 为据因拒绝 promise
      (5) 如果 then 不是函数,以 x 为参数执行 promise

    • 如果 x 不为对象或者函数,以 x 为参数执行 promise

注:如果一个 promise 被一个循环的 thenable 链中的对象解决,而 [[Resolve]](promise, thenable)的递归性质又使得其被再次调用,根据上述的算法将会陷入无限递归之中。算法虽不强制要求,但也鼓励施者检测这样的递归是否存在,若检测到存在则以一个可识别的TypeError为据因来拒绝 promise。

那么根据定义我们来完善一下先前空实现的resolve方法:

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
/**
* Promise解决过程
* @param {Promise} promise2
* @param {value} x
*/
function resolve(promise2, x) {
if (promise2 === x) {
// 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
reject.call(promise2, new TypeError('x 与 promise2 不能相等'))
} else if (x && x instanceof Promise) {
if (x.state === State.PENDING) {
// 如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝
x.then(function (value) {
resolve(promise2, value)
}, function (reason) {
reject.call(promise2, reason)
})
} else if (x.state === State.FULFILLED) {
// 如果 x 处于执行态,用相同的值执行 promise
resolve(promise2, x.value)
} else {
// 如果 x 处于拒绝态,用相同的据因拒绝 promise
reject.call(promise2, x.reason)
}
} else if (isObject(x) || isFunction(x)) {
// x 为对象或函数时
// 由于不允许调用多次,采用一个变量记录是否已经被调用过
let called = false
try {
// 把 `x.then` 赋值给 then
const then = x.then

// 如果then是个函数
if (isFunction(then)) {

then.call(x, function (y) {
// resolvePromise
if (!called) {
called = true
resolve(promise2, y)
}
}, function (r) {
// resolvePromise
if (!called) {
called = true
reject.call(promise2, r)
}
})

} else {
// 如果不是函数
fulfill.call(promise2, x)
}
} catch (e) {
if (!called) {
// 如果已经调用过则忽略
// 如果取 `x.then` 的值时抛出错误 e ,则以 e 为据因拒绝 promise
reject.call(promise2, e)
}
}
} else {
// 如果 x 不为对象或者函数
fulfill.call(promise2, x)
}
}

看到这里问题来了,Promise的异步概念还没有体现到,这是因为规范后面有一个注释:

有英文能力的可以看一下原文:https://promisesaplus.com/#notes

注释

在本文第四点提到的平台代码指的是引擎、环境以及 promise 的实施代码。实践中要确保 onFulfilledonRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。这个事件队列可以采用“宏任务(macro-task)”机制或者“微任务(micro-task)”机制来实现。由于 promise 的实施代码本身就是平台代码(译者注:即都是 JavaScript),故代码自身在处理在处理程序时可能已经包含一个任务调度队列。

译者注:这里提及了 macrotask 和 microtask 两个概念,这表示异步任务的两种分类。在挂起任务时,JS 引擎会将所有任务按照类别分到这两个队列中,首先在 macrotask 的队列(这个队列也被叫做 task queue)中取出第一个任务,执行完毕后取出 microtask 队列中的所有任务顺序执行;之后再取 macrotask 任务,周而复始,直至两个队列的任务都取完。

两个类别的具体分类如下:

  • macro-task: script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering
  • micro-task: process.nextTick, Promises(这里指浏览器实现的原生 Promise), Object.observe, MutationObserver

详见 stackoverflow 解答这篇博客

使用setTimeout模拟异步处理

最终完善一下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
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
/**
* 尝试手写实现Promise
*/

function isFunction(check) {
return (typeof check === 'function' || Object.prototype.toString.call(check) === '[object Function]')
}

function isObject(check) {
return check === Object(check)
}

// promise 的三种状态
const State = {
PENDING: Symbol.for('pending'),
FULFILLED: Symbol.for('fulfilled'),
REJECTED: Symbol.for('rejected')
}

/**
* this需要绑定指向Promise实例
* @param {State} state
*/
function changeState(state) {
if (this.state === state) {
throw new Error('不能迁移至相同状态')
}
if (state === State.PENDING) {
throw new Error('不允许迁移至Pending状态')
}
if (this.state === State.FULFILLED || this.state === State.REJECTED) {
throw new Error('不能迁移至其他任何状态')
}
this.state = state
}


function fulfill(value) {
try {
changeState.call(this, State.FULFILLED)
this.value = value
// 当状态改变时,需要遍历执行队列中的任务
this.fulfillQueue.forEach(function (fn) { fn() })
} catch (e) { }
}

function reject(reason) {
try {
changeState.call(this, State.REJECTED)
this.reason = reason
// 当状态改变时,需要遍历执行队列中的任务
this.rejectQueue.forEach(function (fn) { fn() })
} catch (e) { }
}

/**
* Promise解决过程
* @param {Promise} promise2
* @param {value} x
*/
function resolve(promise2, x) {
if (promise2 === x) {
// 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
reject.call(promise2, new TypeError('x 与 promise2 不能相等'))
} else if (x && x instanceof Promise) {
if (x.state === State.PENDING) {
// 如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝
x.then(function (value) {
resolve(promise2, value)
}, function (reason) {
reject.call(promise2, reason)
})
} else if (x.state === State.FULFILLED) {
// 如果 x 处于执行态,用相同的值执行 promise
resolve(promise2, x.value)
} else {
// 如果 x 处于拒绝态,用相同的据因拒绝 promise
reject.call(promise2, x.reason)
}
} else if (isObject(x) || isFunction(x)) {
// x 为对象或函数时
// 由于不允许调用多次,采用一个变量记录是否已经被调用过
let called = false
try {
// 把 `x.then` 赋值给 then
const then = x.then

// 如果then是个函数
if (isFunction(then)) {

then.call(x, function (y) {
// resolvePromise
if (!called) {
called = true
resolve(promise2, y)
}
}, function (r) {
// resolvePromise
if (!called) {
called = true
reject.call(promise2, r)
}
})

} else {
// 如果不是函数
fulfill.call(promise2, x)
}
} catch (e) {
if (!called) {
// 如果已经调用过则忽略
// 如果取 `x.then` 的值时抛出错误 e ,则以 e 为据因拒绝 promise
reject.call(promise2, e)
}
}
} else {
// 如果 x 不为对象或者函数
fulfill.call(promise2, x)
}
}

/**
* Promise类
*/
function Promise(executor) {
this.state = State.PENDING
this.value = undefined
this.reason = undefined

// 保存异步执行的队列 { fulfill, reject }
this.fulfillQueue = []
this.rejectQueue = []

if (isFunction(executor)) {
try {
executor(fulfill.bind(this), reject.bind(this))
} catch (e) {
reject.call(this, e)
}
}
}

Promise.prototype.then = function (onFulfilled, onRejected) {
const _self = this
// promise2 必须成功执行并返回相同的值
onFulfilled = isFunction(onFulfilled) ? onFulfilled : function (v) { return v }
// promise2 必须拒绝执行并返回相同的据因
onRejected = isFunction(onRejected) ? onRejected : function (err) { throw err }

const promise2 = new Promise(function () {
if (_self.state === State.FULFILLED) {
// 重点步骤
setTimeout(function () {
try {
// 如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程
const x = onFulfilled(_self.value)
resolve(promise2, x)
} catch (e) {
// 如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e
reject.call(promise2, e)
}
}, 0)
} else if (_self.state === State.REJECTED) {
// 重点步骤
setTimeout(function () {
try {
// 如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程
const x = onRejected(_self.reason)
resolve(promise2, x)
} catch (e) {
// 如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e
reject.call(promise2, e)
}
}, 0)
} else {
// 当还没执行时不可以被调用,那么需要将此保存起来,在后续状态改变后调用
_self.fulfillQueue.push(function () {
// 重点步骤
setTimeout(function () {
try {
// 如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程
const x = onFulfilled(_self.value)
resolve(promise2, x)
} catch (e) {
// 如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e
reject.call(promise2, e)
}
}, 0)
})

_self.rejectQueue.push(function () {
// 重点步骤
setTimeout(function () {
try {
// 如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程
const x = onRejected(_self.reason)
resolve(promise2, x)
} catch (e) {
// 如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e
reject.call(promise2, e)
}
}, 0)
})
}
})

return promise2
}

module.exports = Promise

测试手写Promise/A+代码

需要安装mocha以及promises-aplus-tests

1
$ npm i -D mocha promises-aplus-tests

新建test.js文件,并添加以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var Promise = require('./promise-step');

var adapter = (function() {
var res, rej;
return {
deferred: function() {
return {
promise: new Promise(function(resolve, reject){
res = resolve;
rej = reject;
}),
resolve: res,
reject: rej,
}
}
}
})();

describe("Promises/A+ Tests", function () {
require("promises-aplus-tests").mocha(adapter);
});

手动执行命令

1
$ ./node_modules/mocha/bin/mocha

或者在package.json中添加scripts

1
2
3
"scripts": {
"test": "mocha"
}

部分运行结果如下

1
2
3
4
5
6
7
8
9
10
    The value is `1` with `Number.prototype` modified to have a `then` method
✓ already-fulfilled
✓ immediately-fulfilled
✓ eventually-fulfilled (56ms)
✓ already-rejected
✓ immediately-rejected
✓ eventually-rejected (56ms)


872 passing (16s)

Promise的API

Promise.resolve

我们来看看MDN上的定义:Promise.resolve()

**Promise.resolve(value)**方法返回一个以给定值解析后的Promise 对象。如果这个值是一个 promise ,那么将返回这个 promise如果这个值是thenable(即带有"then"方法),返回的promise会“跟随”这个thenable的对象,采用它的最终状态;否则返回的promise将以此值完成。此函数将类promise对象的多层嵌套展平。

根据定义我们来手写实现一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Promise.resolve = function (value) {
// 如果value是个Promise,那么直接返回
if (value && value instanceof Promise) {
return value
}
const promise = new Promise(function (resolve, reject) {
if (isFunction(value.then)) {
x.then(function(v) {
resolve(v)
}, function(e) {
reject(e)
})
} else {
resolve(value)
}
})
return promise
}

测试一下:

1
2
3
4
5
6
7
8
9
Promise.resolve(new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok');
}, 3000);
})).then(data=>{
console.log(data,'success')
}).catch(err=>{
console.log(err,'error')
})

控制台等待 3s 后输出:

1
"ok success"

Promise.reject

Promise.reject()方法返回一个带有拒绝原因的Promise对象。

1
2
3
4
5
Promise.reject = function (reason) {
return new Promise(function (_, reject) {
reject(reason)
})
}

Promise.prototype.catch

catch() 方法返回一个Promise (en-US),并且处理拒绝的情况。它的行为与调用Promise.prototype.then(undefined, onRejected) 相同。

1
2
3
Promise.prototype.catch = function (callback) {
return this.then(null, callback)
}

Promise.prototype.finally

finally() 方法返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。这为在Promise是否成功完成后都需要执行的代码提供了一种方式。

这避免了同样的语句需要在then()catch()中各写一次的情况。

注意:finally回调中 throw(或返回被拒绝的promise)将以 throw() 指定的原因拒绝新的promise.

1
2
3
4
5
6
7
Promise.prototype.finally = function (callback) {
return this.then(function(value) {
return Promise.resolve(callback()).then(function() { return value })
}, function (err) {
return Promise.resolve(callback()).then(function() { throw err })
})
}

Promise.all

Promise.all() 方法接收一个promise的iterable类型(注:Array,Map,Set都属于ES6的iterable类型)的输入,并且只返回一个Promise实例, 那个输入的所有promise的resolve回调的结果是一个数组。这个Promise的resolve回调执行是在所有输入的promise的resolve回调都结束,或者输入的iterable里没有promise了的时候。它的reject回调执行是,只要任何一个输入的promise的reject回调执行或者输入不合法的promise就会立即抛出错误,并且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
Promise.all = function(arr) {
if (!Array.isArray(arr)) {
throw new TypeError('param is not iterable')
}
return new Promise(function(resolve, reject) {
const resultArr = []
// 已处理计数
let processCount = 0
const processResultByIndex = function(result, i) {
resultArr[i] = result
// 已处理计数 + 1
processCount += 1

// 如果计数等于arr长度则代表已处理完毕,则执行resolve
if (processCount === arr.length) {
resolve(resultArr)
}
}

for (let i = 0; i < arr.length; i++) {
const item = arr[i]
if (item && typeof item.then === 'function') {
// is a promise or thenable
item.then(function (value) {
processResultByIndex(value, i)
}, reject)
} else {
// normal value
processResultByIndex(item, i)
}
}
})
}

测试一下

1
2
3
4
5
6
7
8
9
10
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values);
});
// expected output: Array [3, 42, "foo"]

总结

从 promise 的使用方法入手,构造出了 promise 的大致框架,然后根据 promise/A+ 规范填充代码,重点实现了 then 的链式调用和值的穿透;然后使用测试脚本对所写的代码是否符合规范进行了测试;最后完成了 Promise 的 API 的实现。弄懂 promise 其实并不复杂,归根结底还是孰能生巧。

且涉及到了JS 的循环机制EventLoop(主线程、微任务、渲染、宏任务)。

代码已放置Github:es5实现es6实现

坚持技术分享,您的支持将鼓励我继续创作!