2
0
0
手写Promise,我终于搞懂了异步编程

文章摘要
|
学前端如果只能选一个东西彻底吃透,我会选Promise。
不是因为面试常考,而是理解了Promise,就理解了JavaScript最核心的异步模型。从回调地狱到async/await,整个演进脉络全在Promise这一条线上。
花了一个周末自己实现了一个符合Promise/A+规范的简易版本,分享核心代码和思路。
第一步:构造函数和状态管理
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
constructor(executor) {
this.status = PENDING
this.value = undefined
this.reason = undefined
this.onFulfilledCallbacks = []
this.onRejectedCallbacks = []
const resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED
this.value = value
this.onFulfilledCallbacks.forEach(fn => fn())
}
}
const reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED
this.reason = reason
this.onRejectedCallbacks.forEach(fn => fn())
}
}
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
}关键点:状态一旦改变就不能再变,所以resolve和reject里都要判断status是否为PENDING。
第二步:then方法的实现
then是Promise最核心的方法,也是最复杂的部分:
then(onFulfilled, onRejected) {
// 参数可选且必须为函数
onFulfilled = typeof onFulfilled === 'function'
? onFulfilled
: value => value
onRejected = typeof onRejected === 'function'
? onRejected
: reason => { throw reason }
const promise2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
setTimeout(() => {
try {
const x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
}
if (this.status === REJECTED) {
setTimeout(() => {
try {
const x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
}
if (this.status === PENDING) {
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
})
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
})
}
})
return promise2
}这里有几个关键设计:
setTimeout包裹——保证then是异步执行的,符合微任务规范(这里简化用宏任务)
返回新的Promise——实现链式调用的关键
resolvePromise函数——处理各种返回值情况,包括返回普通值、新Promise、甚至thenable对象
第三步:resolvePromise核心解析
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected'))
}
let called = false
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {
const then = x.then
if (typeof then === 'function') {
then.call(
x,
(y) => {
if (called) return
called = true
resolvePromise(promise2, y, resolve, reject)
},
(r) => {
if (called) return
called = true
reject(r)
}
)
} else {
resolve(x)
}
} catch (error) {
if (called) return
called = true
reject(error)
}
} else {
resolve(x)
}
}这个函数解决了Promise可以resolve任意值的问题——不管是普通值、Promise对象,还是其他实现了then方法的对象,都能正确处理。
第四步:工具方法
// Promise.resolve
static resolve(value) {
if (value instanceof MyPromise) return value
return new MyPromise((resolve) => resolve(value))
}
// Promise.reject
static reject(reason) {
return new MyPromise((_, reject) => reject(reason))
}
// Promise.all
static all(promises) {
return new MyPromise((resolve, reject) => {
const results = []
let count = 0
promises.forEach((p, index) => {
MyPromise.resolve(p).then(
(value) => {
results[index] = value
count++
if (count === promises.length) resolve(results)
},
(reason) => reject(reason)
)
})
})
}
// Promise.race
static race(promises) {
return new MyPromise((resolve, reject) => {
promises.forEach((p) => {
MyPromise.resolve(p).then(resolve, reject)
})
})
}写完这个实现,再看async/await,感觉像从看魔术变成了看说明书——原来一切都是有迹可循的。
如果你也在学异步编程,强烈建议手写一遍Promise。代码量不大,但收获是看十篇文章都比不上的。
