你是否曾在回调地狱里迷失方向?是否曾被异步代码折磨得怀疑人生?今天,让我们用一份幽默风趣的技术博客,带你彻底搞懂Promise的原理、实现与实战!
一、前言:回调地狱的自我救赎
在JavaScript的世界里,异步如影随形。你以为setTimeout只是个定时器,结果它成了回调地狱的入口。来看看最经典的回调地狱:
function A() {
try {
setTimeout(() => {
console.log('A')
B()
}, 1000)
} catch (error) {
console.log('A错误', error)
}
}
function B() {
setTimeout(() => {
console.log('B')
}, 500)
}
A()
是不是感觉代码像套娃?一层套一层,出错了还不好找!
二、Promise的横空出世
2.1 Promise是什么?
Promise是JavaScript异步编程的救世主!它是一个构造函数,用于封装异步操作,解决回调地狱,让代码优雅如诗。
2.2 Promise的三大状态
- pending(待定):初始状态,啥都没发生
- fulfilled(已成功):异步操作成功
- rejected(已失败):异步操作失败
状态一旦改变就不可逆,谁也别想反悔!
2.3 Promise的基本用法
function A() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('A')
resolve('ok')
}, 1000)
})
}
A()
.then((res) => {
console.log(res);
return 'hello'
})
.then((res2) => {
console.log(res2);
})
链式调用,优雅如斯!
三、Promise的设计原理与实现
3.1 设计原理
- 解决回调地狱、错误处理困难、控制流复杂
- 状态机模式,三种状态不可逆
- 链式调用,返回新Promise
- 统一接口,所有异步操作都遵循Promise规范
3.2 架构设计要点
- 构造函数接收executor,立即执行
- 回调队列,支持多次then
- 异步调度,setTimeout/queueMicrotask
- 错误传播,catch一把抓
- 值传递,Promise解析过程
四、手写Promise:MyPromise源码全解析
让我们撸起袖子,手写一个属于自己的Promise!
class MyPromise {
constructor(executor) {
this.state = 'pending'
this.onFulfilledCallbacks = []
this.onRejectedCallbacks = []
this.value = undefined
this.reason = undefined
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled'
this.value = value
this.onFulfilledCallbacks.forEach(fn => fn(value))
}
}
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected'
this.reason = reason
this.onRejectedCallbacks.forEach(fn => fn(reason))
}
}
executor(resolve, reject)
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) => value
onRejected = typeof onRejected === 'function' ? onRejected : (reason) => { throw reason }
const newPromise = new MyPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
const result = onFulfilled(this.value)
resolve(result)
} catch (error) {
reject(error)
}
}, 0)
}
if (this.state === 'rejected') {
setTimeout(() => {
try {
const result = onRejected(this.reason)
resolve(result)
} catch (error) {
reject(error)
}
}, 0)
}
if (this.state === 'pending') {
this.onFulfilledCallbacks.push((value) => {
setTimeout(() => {
try {
const result = onFulfilled(value)
resolve(result)
} catch (error) {
reject(error)
}
}, 0)
})
this.onRejectedCallbacks.push((reason) => {
setTimeout(() => {
try {
const result = onRejected(reason)
resolve(result)
} catch (error) {
reject(error)
}
}, 0)
})
}
})
return newPromise
}
// ...静态方法和finally/catch实现略,详见源码
}
是不是感觉自己离大佬又近了一步?
五、Promise静态方法的手写实现与讲解
下面我们来手写实现Promise的几个常用静态方法,并配上了详细注释和讲解,让你一看就懂,一学就会!
5.1 Promise.race:谁跑得快,谁赢!🏃♂️🏃♀️
static race(promises) {
// 返回一个新的Promise
return new MyPromise((resolve, reject) => {
// 遍历所有Promise
for (let promise of promises) {
// 只要有一个成功或失败,立即结束
promise.then(
(value) => {
resolve(value) // 谁先成功就用谁
},
(reason) => {
reject(reason) // 谁先失败就用谁
}
)
}
})
}
讲解:
- race方法就是“赛跑”,谁先完成(无论成功还是失败),就以它的结果为最终结果。
- 适合做超时控制、竞速请求等场景。
5.2 Promise.all:全员到齐才算成功👨👩👧👦
static all(promises) {
const result = [] // 用于收集所有成功的结果
let count = 0 // 计数器,统计完成的Promise数量
return new MyPromise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
promises[i].then(
(value) => {
result[i] = value // 按顺序收集结果
count++
if (count === promises.length) {
resolve(result) // 全部成功才resolve
}
},
(reason) => {
reject(reason) // 只要有一个失败就reject
}
)
}
})
}
讲解:
- all方法是“全员到齐”,只有所有Promise都成功才算成功,否则只要有一个失败就直接失败。
- 适合并发执行多个异步操作,需要所有操作都成功的场景。
5.3 Promise.any:只要有一个成功就行🍀
static any(promises) {
const result = [] // 收集所有失败原因
let count = 0 // 统计失败数量
return new MyPromise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
promises[i].then(
(value) => {
resolve(value) // 只要有一个成功就resolve
},
(reason) => {
count++
result[i] = reason // 收集失败原因
if (count === promises.length) {
// 全部失败才reject,并返回AggregateError
reject(new AggregateError(result, 'All promises were rejected'))
}
}
)
}
})
}
讲解:
- any方法是“幸运儿”,只要有一个Promise成功就算成功,全部失败才算失败。
- 适合多个备选方案,只要有一个成功即可。
5.4 Promise.allSettled:不管成败,都要结果📦
static allSettled(promises) {
const result = [] // 收集所有结果
let count = 0 // 统计完成数量
return new MyPromise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
promises[i].then(
(value) => {
result[i] = {
status: 'fulfilled',
value
}
},
(reason) => {
result[i] = {
status: 'rejected',
reason
}
}
).finally(() => {
count++
if (count === promises.length) {
resolve(result) // 全部完成后返回所有结果
}
})
}
})
}
讲解:
- allSettled方法是“全员汇报”,无论成功还是失败都要给个交代。
- 适合需要知道所有操作最终状态的场景。
5.5 Promise.resolve / Promise.reject:快速创建Promise
static resolve(value) {
// 直接返回一个fulfilled状态的Promise
return new MyPromise((resolve) => {
resolve(value)
})
}
static reject(reason) {
// 直接返回一个rejected状态的Promise
return new MyPromise((_, reject) => {
reject(reason)
})
}
讲解:
- resolve方法用于快速创建一个成功的Promise。
- reject方法用于快速创建一个失败的Promise。
5.6 finally方法:无论如何都要收尾🧹
finally(callback) {
// callback 无论成功失败都要执行
return this.then(
(value) => {
callback()
return value
},
(reason) => {
callback()
throw reason
}
)
}
讲解:
- finally方法无论Promise成功还是失败都会执行,适合做清理工作。
- 不会改变Promise的值,只是添加收尾逻辑。
以上就是Promise各大静态方法的手写实现和详细讲解,配合注释和表情包,助你轻松掌握异步编程的核心技能!🎉
六、Promise的常见坑与进阶技巧
6.1 setTimeout模拟微任务?
MyPromise用setTimeout模拟微任务,其实标准Promise用的是queueMicrotask。虽然效果类似,但setTimeout是宏任务,可能导致执行顺序不同。
6.2 Promise解析过程
标准Promise会自动解析then返回的Promise,MyPromise实现略有不足,进阶可参考Promise/A+规范。
6.3 错误处理与值穿透
- then/catch返回的新Promise支持链式调用
- 回调不是函数时会值穿透
- 错误会沿链传播,直到被catch捕获
七、总结与展望
Promise让异步编程变得优雅、可控,彻底告别回调地狱。手写Promise不仅能提升你的编码能力,还能让你在面试中脱颖而出!
彩蛋
- 回调地狱:😱
- Promise链式调用:😎
- finally收尾:🧹
- all全员到齐:👨👩👧👦
- race竞速:🏃♂️🏃♀️
- any有一个成功就行:🍀
- allSettled不管成败都要结果:📦
如果你觉得本文有趣又有用,期待点赞!让更多人告别回调地狱,拥抱Promise的美好生活!🎉