JS异步编程终极秘籍:手写Promise,笑谈回调地狱

111 阅读5分钟

你是否曾在回调地狱里迷失方向?是否曾被异步代码折磨得怀疑人生?今天,让我们用一份幽默风趣的技术博客,带你彻底搞懂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的美好生活!🎉