Promise 对象
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了 Promise 对象。
Promise 对象有以下两个特点。
(1)对象的状态不受外界影响。
Promise 对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise 这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise 对象的状态改变,只有两种可能:从 pending 变为 fulfilled 和从 pending 变为 rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。
下面通过手写一个符合Promises/A+规范的 Promise 来深入理解 Promise。
构造函数的实现
ES6 规定,Promise 对象是一个构造函数,用来生成 Promise 实例。
分析
参考下面原生的 Promise 写法,可以得出结论:
const p = new Promise((resolve, reject) => {
resolve()
})
- Promise 构造函数接收一个函数(假设命名为:fn)作为参数。
- fn 的两个参数分别是
resolve
和reject
,它们同样也是函数。 - 在 Promise 中 fn 是同步执行的。
使用class关键字定义一个MyPromise
类。接受一个 executor 方法并调用(调用的原因:上述分析中的第 3 条)。
class MyPromise {
constructor(executor) {
executor()
}
}
可以在constructor
中创建resolve
和reject
函数。
class MyPromise {
constructor(executor) {
const resolve = () => {}
const reject = () => {}
executor()
executor(resolve, reject)
}
}
探讨resolve
和reject
函数定义的位置
在constructor
中创建resolve
和reject
函数,每次实例化的时候都会创建两个新的函数。但是不能把这两个函数放在原型上面,使每一个 Promise 对象都共用这两个方法。下面是错误的做法:
class MyPromise {
constructor(executor) {
executor(this.resolve, this.reject)
}
resolve() {
// this
}
reject() {}
}
原因就在于如果这样写的话,line 6 的 this 指向会有问题。因为函数中 this 的指向取决于该函数的调用方式。而resolve
函数是在外部被调用的,因此 this 指向全局对象 window(node 环境中是 global)。
如果一定要在原型上定义resolve
和reject
函数。那就需要下面这种写法:
class Mypromise {
constructor(executor) {
executor(this.resolve, this.reject)
executor(this.resolve.bind(this), this.reject.bind(this))
}
resolve() {}
reject() {}
}
但是这样做的话每次调用 bind()函数也会返回一个新的函数,还不如直接在constructor
中定义这两个方法。
resolve
函数可以接收一个参数,不传就是 undefined。reject
函数也接收一个错误对象作为参数,表示错误的原因。
class MyPromise {
constructor(executor) {
const resolve = (res) => {}
const reject = (err) => {}
executor(resolve, reject)
}
}
resolve
函数的作用是:
- 将 Promise 对象的状态从“未完成”变为“成功”(即从 pending 变为 fulfilled)。
- 返回当前 Promise 的结果。
示例图
因此在 Promise 中需要定义一个状态(state)属性和一个结果(result)属性。
class MyPromise {
constructor(executor) {
this._state = 'pending'
this._result = undefined
const resolve = (res) => {}
const reject = (err) => {}
executor(resolve, reject)
}
}
定义私有属性
_state
和_result
使用下划线_
的原因是这两个属性是内部属性,不希望外部访问。这种写法只是通过命名规范进行一个轻量级的约束。这种写法在外部依然可以访问到这两个属性。
参考这篇文章。可以像下面这样,通过#
来定义私有属性。
class MyPromise {
#state = 'pending'
#result = undefined
constructor(executor) {
this._state = 'pending'
this._result = undefined
const resolve = (res) => {}
const reject = (err) => {}
executor(resolve, reject)
}
}
补充resolve
函数和reject
函数要做的事情。
class MyPromise {
#state = 'pending'
#result = undefined
constructor(executor) {
const resolve = (res) => {
this.#state = 'fulfilled'
this.#result = res
}
const reject = (err) => {
this.#state = 'rejected'
this.#result = err
}
executor(resolve, reject)
}
}
根据 Promise 的第二个特点,需要在调用resolve
和reject
方法的时候添加状态是否已被修改的判断。如果状态被修改了,则什么也不做。
constructor(executor) {
const resolve = (res) => {
if (this.#state !== 'pending') return
this.#state = 'fulfilled'
this.#result = res
}
const reject = (err) => {
if (this.#state !== 'pending') return
this.#state = 'rejected'
this.#result = err
}
executor(resolve, reject)
}
将resolve
和reject
方法中的操作提取成一个私有函数changeState
。
class MyPromise {
#state = 'pending'
#result = undefined
constructor(executor) {
const resolve = (res) => {
this.#changeState('fulfilled', res)
}
const reject = (err) => {
this.#changeState('rejected', err)
}
}
#changeState(state, result) {
if (this.#state !== 'pending') return
this.#state = state
this.#result = result
}
}
避免硬编码(hard code)
定义PENDING
、FULFILLED
、REJECTED
常量。并在使用到pending
、fulfilled
、rejcted
的地方进行替换。
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejcted'
#state = 'pending'
#state = PENDING
const resolve = (res) => {
this.#changeState('fulfilled', res)
this.#changeState(FULFILLED, res)
}
const reject = (err) => {
this.#changeState('rejected', err)
this.#changeState(REJECTED, err)
}
#changeState(state, result) {
if (this.#state !== 'pending') return
if (this.#state !== PENDING) return
}
executor
函数中的错误处理。
示例图
示例图中可以看到如果executor
函数执行发生错误的话,Promise 的状态会被改变为rejected
,并且会返回错误信息。 将executor
函数的调用放入try catch
中。
class MyPromise {
constructor(executor) {
const resolve = (res) => {}
const reject = (err) => {}
executor(resolve, reject)
try {
executor(resolve, reject)
} catch (err) {
reject(err)
}
}
}
注意
此处的try catch
只能捕获同步错误。对于异步错误,目前官方的 Promise 也无法捕获异步错误,因此异步错误无法影响 Promise 的状态。
示例图
手写 Promise ——构造函数的完整代码
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
#state = PENDING
#result = undefined
constructor(executor) {
const resolve = (res) => {
this.#changeState(FULFILLED, res)
}
const reject = (err) => {
this.#changeState(REJECTED, err)
}
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
#changeState(state, result) {
if (this.#state !== PENDING) return
this.#state = state
this.#result = result
}
}
then 的回调(onFulfilled、onRejected)执行时机
Promises/A+规范中着重强调了then
方法: A promise must provide a
then
method to access its current or eventual value or reason.
分析
通过阅读 Promises/A+规范中对then
方法的描述,可以得出以下结论:
- Promise 的 then 方法接收两个可选参数,
onFulfilled
和onRejected
。- 如果
onFulfilled
是一个函数:
它必须在 Promise 状态是 fulfilled 之后调用,并以 Promise 的值作为第一个参数。
它不能被多次调用。 - 如果
onRejected
是一个函数:
它必须在 Promise 状态是 rejected 之后调用,并以 Promise 的原因作为第一个参数。
它不能被多次调用。
- 如果
- then 可能会针对同一个 Promise 被多次调用。
- then 必须返回一个 Promise。
定义then
函数签名。接收两个可选参数:onFulfilled
和onRejected
,并返回一个 Promise。
class MyPromise {
then(onFulfilled, onRejected) {
return new MyPromise(resolve, reject)
}
}
onFulfilled
调用时机:当 Promise 状态为 fulfilled 时(pending ——> fulfilled)调用,并将结果#result
传入。onRejected
调用时机:当 Promise 状态为 rejected 时(pending ——> rejected)调用,并将结果#result
传入。
class MyPromise {
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
if (this.#state === FULFILLED) {
onFulfilled(this.#result)
} else if (this.#state === REJECTED) {
onRejected(this.#result)
}
})
}
}
const p = new MyPromise((resolve, reject) => {
resolve(111)
})
p.then((res) => {
console.log('promise 完成', res) // promise 完成 111
})
const p = new MyPromise((resolve, reject) => {
reject(222)
})
p.then((err) => {
console.log('promise 失败', err) // promise 失败 222
})
但是这个then
方法此时并不能处理异步的情况,如下所示:
const p = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(111)
}, 1000)
})
p.then(
(res) => {
console.log(res) // 不会输出任何内容
},
(err) => {
console.log(err) // 不会输出任何内容
}
)
原因
在上面使用了 setTimeout(() => {resolve(111)}, 1000)的情况下,得到的 Promise p
是挂起状态。1000ms 之后才会完成。当运行到 p.then()方法时,由于 Promise 是挂起状态,所以既不会执行onFulfilled
回调也不会执行onRejected
回调。1000ms 之后也不会执行,因为 then()方法已经执行结束了,不会有机会再次执行onFulfilled
和onRejected
。
then
方法中无法获取到当前 Promise 什么时候完成,什么时候拒绝。但是#changeState
方法可以知道,因为#changeState
方法的作用就是改变 Promise 的状态,而#changeState
方法中又无法使用then
方法的onFulfilled
、onRejected
回调。
因此,可以在then
方法中只把onFulfilled
、onRejected
,以及 return 的resolve
、reject
记录到某个私有属性中,而不是直接处理逻辑。
在then
方法中去调用一个私有方法#run
,来处理上一步记录到私有属性中的那些函数。
示例图
class MyPromise {
#handler = undefined
#changeState(state, result) {
// ...
this.#run()
}
#run() {}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
this.#handler = {
onFulfilled,
onRejected,
resolve,
reject,
}
this.#run()
})
}
}
注意
由于then
方法可以被多次调用,因此#handler
属性存放的应该是一个数组。
示例图
class MyPromise {
#handlers = []
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
this.#handlers.push({
onFulfilled,
onRejected,
resolve,
reject,
})
this.#run()
})
}
}
实现#run
方法:
- 过滤掉 pending 状态。
- 从
#handlers
数组中依次取出每一项。- 如果当前状态是 fulfilled,则判断
onFulfilled
是否是函数,是则执行,并传入结果。 - 如果当前状态是 rejected,则判断
onRejected
是否是函数,是则执行,并传入结果。
- 如果当前状态是 fulfilled,则判断
class MyPromise {
#run() {
if (this.#state === PENDING) return
while (this.#handlers.length) {
const { onFulfilled, onRejected, resolve, reject } =
this.#handlers.shift()
if (this.#state === FULFILLED) {
if (typeof onFulfilled === 'function') {
onFulfilled(this.#result)
}
} else {
if (typeof onRejected === 'function') {
onRejected(this.#result)
}
}
}
}
}
至此,then 的回调(onFulfilled、onRejected)执行时机就解决了。
then 的返回值
情况一:对应的回调不是函数。
- 状态是 fulfilled,但是传入的
onFulfilled
不是函数。 - 状态是 rejected,但是传入的
onRejected
不是函数。
p.then(111, (err) => {})
p.then((res) => {}, 222)
这种情况下then
返回的 Promise 应该和 p
的结果一致。
#run() {
if (this.#state === PENDING) return
while (this.#handlers.length) {
const { onFulfilled, onRejected, resolve, reject } = this.#handlers.shift()
if (this.#state === FULFILLED) {
if (typeof onFulfilled === 'function') {
onFulfilled(this.#result)
} else {
resolve(this.#result)
}
}
// ...
}
}
p.then(111, (err) => {})
#run() {
if (this.#state === PENDING) return
while (this.#handlers.length) {
const { onFulfilled, onRejected, resolve, reject } = this.#handlers.shift()
if (this.#state === FULFILLED) {
// ...
} else {
if (typeof onRejected === 'function') {
onRejected(this.#result)
} else {
reject(this.#result)
}
}
}
}
p.then((res) => {}, 222)
情况二:传入的onFulfilled
和onRejected
是函数。需要考虑函数执行的结果。因此包裹在try catch
中执行。
#run() {
// ...
if (this.#state === FULFILLED) {
if (typeof onFulfilled === 'function') {
try {
const res = onFulfilled(this.#result)
resolve(res)
} catch (error) {
reject(error)
}
}
}
}
#run() {
// ...
if (this.#state === REJECTED) {
if (typeof onRejected === 'function') {
try {
const res = onRejected(this.#result)
resolve(res)
} catch (error) {
reject(error)
}
}
}
}
有重复代码,提取成一个#runOne
函数。
#runOne(callback, resolve, reject) {
if (typeof callback !== 'function') {
const handler = this.#state === FULFILLED ? resolve : reject
handler(this.#result)
return
}
try {
const data = callback(this.#result)
resolve(data)
} catch (error) {
reject(error)
}
}
#run() {
if (this.#state === FULFILLED) {
this.#runOne(onFulfilled, resolve, reject)
} else {
// ...
}
}
#run() {
if (this.#state === FULFILLED) {
// ...
} else {
this.#runOne(onRejected, resolve, reject)
}
}
情况三:回调的返回结果是一个 Promise。
#runOne
方法中的try catch
中的 callback()返回的 data 是一个 Promise。- 如何判断返回值 data 是一个 Promise?
- 运行
then
的回调callback()
应该放在微队列中。
PromiseLike(thenable)和微队列
- 判断返回值 data 是一个 Promise。
- 只需要判断 data 是否符合Promises/A+规范。
- 可以通过判断 data 中是否含有
then
属性,且该属性是一个 function。
定义一个辅助函数#isPromise
来判断是否是 Promise。并在try catch
中获取到 callback()的返回值 data 后进行判断。
#isPromise(value) {
if (
value !== null && (typeof value === 'object' ||
typeof value === 'function')
) {
return typeof value.then === 'function'
}
return false
}
#runOne(callback, resolve, reject) {
// ...
try {
const data = callback(this.#result)
if (this.#isPromise(data)) {
data.then(resolve, reject)
} else {
resolve(data)
}
} catch (error) {
reject(error)
}
}
const p = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 1000)
})
p.then((res) => {
console.log(res)
return new Promise((resolve) => {
setTimeout(() => {
resolve(res * 2)
}, 1000)
})
}).then((res) => {
console.log(res)
})
// 1000ms后输出:1
// 再1000ms后输出:2
function delay(duration = 1000) {
return new MyPromise((resolve) => {
setTimeout(resolve, duration)
})
}
async function test() {
await delay()
console.log(111)
}
test()
// 1000ms后输出:111
- 将
callback()
方法加入到微队列中执行。
定义一个辅助函数#runMicroTask
,该函数接收一个 func
入参,作用是将 func
添加到微队列。
注意
微队列是环境赋予的能力,比如 node 环境中的事件循环,浏览器环境中的事件循环。事件循环是环境能力而非语言能力。脱离了环境提供的 API 的话,是无法用标准库中的代码模拟微队列的。
方案
- node 环境中,可以通过调用
process.nextTick(func)
方法将 func 添加到 node 事件循环的微队列中。 - 浏览器环境中:
2.1 使用MutationObserver API。通过调用构造函数MutationObserver(func)
,创建并返回一个新的MutationObserver
它会在指定的 DOM 发生变化时被调用。并且会将 func 放入微队列。
2.2 使用queueMicrotask()方法。
#runMicroTask(func) {
if (typeof process === 'object' && typeof process.nextTick === 'function') {
process.nextTick(func)
} else if (typeof MutationObserver === 'function') {
const ob = new MutationObserver(func)
const textNode = document.createTextNode('1')
ob.observe(textNode, {
characterData: true,
})
textNode.data = '2'
} else {
setTimeout(func, 0)
}
}
#runMicroTask(func) {
if (typeof process === 'object' && typeof process.nextTick === 'function') {
process.nextTick(func)
} else if (typeof queueMicrotask === 'function') {
queueMicrotask(func)
} else {
setTimeout(func, 0)
}
}
function runOne(callback, resolve, reject) {
this.#runMicroTask(() => {
// ...
})
}
setTimeout(() => {
console.log(1)
}, 0)
new MyPromise((resolve) => {
resolve(2)
}).then((res) => {
console.log(res)
})
console.log(3)
// 3
// 2
// 1
这样就完整的实现了一个符合 Promises/A+ 规范的 Promise 代码。
完整代码
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
#state = PENDING
#result = undefined
#handlers = []
constructor(executor) {
const resolve = (res) => {
this.#changeState(FULFILLED, res)
}
const reject = (err) => {
this.#changeState(REJECTED, err)
}
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
#changeState(state, result) {
if (this.#state !== PENDING) return
this.#state = state
this.#result = result
this.#run()
}
#isPromise(value) {
if (
value !== null &&
(typeof value === 'object' || typeof value === 'function')
) {
return typeof value.then === 'function'
}
return false
}
#runMicroTask(func) {
if (typeof process === 'object' && typeof process.nextTick === 'function') {
process.nextTick(func)
} else if (typeof queueMicrotask === 'function') {
queueMicrotask(func)
} else {
setTimeout(func, 0)
}
}
// use MutationObserver
// #runMicroTask(func) {
// if (typeof process === 'object' && typeof process.nextTick === 'function') {
// process.nextTick(func)
// } else if (typeof MutationObserver === 'function') {
// const ob = new MutationObserver(func)
// const textNode = document.createTextNode('1')
// ob.observe(textNode, {
// characterData: true,
// })
// textNode.data = '2'
// } else {
// setTimeout(func, 0)
// }
// }
#runOne(callback, resolve, reject) {
this.#runMicroTask(() => {
if (typeof callback !== 'function') {
const handler = this.#state === FULFILLED ? resolve : reject
handler(this.#result)
return
}
try {
const data = callback(this.#result)
if (this.#isPromise(data)) {
data.then(resolve, reject)
} else {
resolve(data)
}
} catch (error) {
reject(error)
}
})
}
#run() {
if (this.#state === PENDING) return
while (this.#handlers.length) {
const { onFulfilled, onRejected, resolve, reject } =
this.#handlers.shift()
if (this.#state === FULFILLED) {
this.#runOne(onFulfilled, resolve, reject)
} else {
this.#runOne(onRejected, resolve, reject)
}
}
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
this.#handlers.push({
onFulfilled,
onRejected,
resolve,
reject,
})
this.#run()
})
}
}
扩展
尝试实现 catch()、finally()、resolve()、reject()方法。
1. Promise.prototype.catch()
Promise 实例的 catch()
方法用于注册一个在 promise 被拒绝时调用的函数。它会立即返回一个等效的 Promise 对象,这可以允许你链式调用其他 promise 的方法。此方法是 Promise.prototype.then(undefined, onRejected)
的一种简写形式。
catch()
方法内部会调用当前 promise 对象的 then()
方法,并将 undefined
和 onRejected
作为参数传递给 then()
。该调用的返回值直接被返回。
class MyPromise {
// ...
catch(onRejected) {
return this.then(undefined, onRejected)
}
}
const p = new MyPromise((resolve, reject) => {
reject(123)
})
p.catch((err) => {
console.log(err)
})
// 123
2. Promise.prototype.finally()
Promise 实例的 finally()
方法用于注册一个在无论 promise 成功或者失败时都会调用的函数。它会立即返回一个等效的 Promise 对象,这可以允许你链式调用其他 promise 方法。
这可以让你避免在 promise 的 then()
和 catch()
处理器中重复编写代码。
finally()
方法类似于调用 then(onFinally, onFinally)
。然而,有几个不同之处:
- 创建内联函数时,你可以只将其传入一次,而不是强制声明两次或为其创建变量。
onFinally
回调函数不接收任何参数。这种情况恰好适用于你不关心失败原因或成功数值的情况,因此无需提供它。finally()
调用通常是透明的,不会更改原始 promise 的状态。例如:- 与
Promise.resolve(2).then(() => 77, () => {})
不同,它返回一个最终结果为值77
的 promise,而Promise.resolve(2).finally(() => 77)
返回一个最终结果为值2
的 promise。 - 类似地,与
Promise.reject(3).then(() => {}, () => 88)
不同,它返回一个最终结果为值88
的 promise,而Promise.reject(3).finally(() => 88)
返回一个最终以原因3
拒绝的 promise。
- 与
class MyPromise {
finally(onFinally) {
return this.then(
(res) => {
onFinally()
return res
},
(err) => {
onFinally()
throw err
}
)
}
}
const p = new MyPromise((resolve, reject) => {
resolve(123)
})
p.finally(() => {
console.log('finally')
})
// finally
3. Promise.resolve()
Promise.resolve()
静态方法将给定的值转换为一个 Promise。如果该值本身就是一个 Promise,那么该 Promise 将被返回;如果该值是一个 thenable 对象,Promise.resolve()
将调用其 then()
方法及其两个回调函数;否则,返回的 Promise 将会以该值兑现(fulfill)。
class MyPromise {
static resolve(value) {
if (value instanceof MyPromise) return value
let _resolve, _reject
const p = new MyPromise((resolve, reject) => {
_resolve = resolve
_reject = reject
})
if (p.#isPromise(value)) {
value.then(_resolve, _reject)
} else {
_resolve(value)
}
return p
}
}
const p = new MyPromise((resolve, reject) => {
resolve(123)
})
console.log(MyPromise.resolve(p) === p)
MyPromise.resolve(p).then((res) => {
console.log('fulfilled', res)
})
MyPromise.resolve(111).then((res) => {
console.log(res)
})
// true
// fulfilled 123
// 111
4. Promise.reject()
Promise.reject()
静态方法返回一个已拒绝(rejected)的 Promise 对象,拒绝原因为给定的参数。
class MyPromise {
static reject(reason) {
return new MyPromise((resolve, reject) => {
reject(reason)
})
}
}
MyPromise.reject(12345).catch((err) => {
console.log(err)
})
// 12345