JS—Promise:原理解析

个人博客:haichenyi.com。感谢关注

一. 目录

二. Promise 基础概念回顾

上一篇掌握promise中就已经讲过了

  1. 什么是 Promise?
  • promise是JavaScript中在ES6中新增的异步操作的解决方案
  • 本质是一个状态机,包含三种状态:pending(等待),fulfilled(成功),rejected(失败)
  • 通过.then()方法链式调用,解决传统回调地狱的问题
  1. 核心特新
  • 状态不可逆: 一旦状态变为fulfilled或者rejected之后,就不可改变了
  • 链式调用: 每个.then()返回一个新的promise
  • 错误冒泡: 链式调用中,错误会一直向后传递,直到被.catch()捕获

三. 深入原理:Promise 如何工作?

  1. 事件循环与微任务(Microtask)
  • 之前异步编程中就讲过了,promise的回调属于微任务,优先级高于宏任务(setTimeout,绑定事件等等)
  • 当promise状态发生改变,.then() 的回调会被放进微任务队列,等待当前宏任务执行完毕之后立即执行
console.log(1);
Promise.resolve().then(() => console.log(2)); // 微任务
setTimeout(() => console.log(3));              // 宏任务
console.log(4);
// 输出顺序:1 → 4 → 2 → 3
  1. 链式调用的实现
  • 每个 .then() 方法都返回一个新的promise,形成链式结构
  • 如果前一个 .then()的回调返回一个值(或新的promise),该值会成为下一个 then()的输入
new Promise((resolve) => resolve(1))
  .then((val) => val + 1)          // 返回 2
  .then((val) => Promise.resolve(val + 1)) // 返回 Promise(3)
  .then(console.log); // 输出 3

链式调用结果图
3. 错误处理机制

  • 如果链式调用中某一步出现错误(reject 或 throw),会跳过后续所有 .then(),直接进入最近的 .catch()。
  • 内部通过 try/catch 捕获回调中的错误,并传递给下一个 Promise。

四. 手写实现 Promise(简化版)

  我们将手把手教你实现一个符合Promise/A+版本的promise,核心逻辑如下:
要看注释,注释才是为什么这么写的思路
要看注释,注释才是为什么这么写的思路
要看注释,注释才是为什么这么写的思路

export default class MyPromise {

    // new Promise((resolve,reject)=>{

    // })
    //1.构造方法需要传递一个executor,包含两个参数(resolve,reject)
    //这两个参数都是方法,并且,方法接收一个参数,resolve(value),reject(reason)
    //要注意,这两个方法是promise内部定义的,并不是外部传递进来的。
    constructor(executor) {

    }
    //2.有三个状态Pending(等待),Fulfilled(成功),Rejected(失败)。
    //并且状态改变不可逆,resolve方法,把状态从pending——>fulfilled
    //reject方法,把状态从pending——>Rejected
    //需要一个成员变量记录当前状态
    static PENDING = "Pending"
    static FULFILLED = "Fulfilled"
    static REJECTED = "Rejected"
    state = MyPromise.PENDING
}

经过上面这两步,我们的类就变成了:

export default class MyPromise {
    //2.有三个状态Pending(等待),Fulfilled(成功),Rejected(失败)。
    //并且状态改变不可逆,resolve方法,把状态从pending——>fulfilled
    //reject方法,把状态从pending——>Rejected
    //需要一个成员变量记录当前状态
    static PENDING = "Pending"
    static FULFILLED = "Fulfilled"
    static REJECTED = "Rejected"
    state = MyPromise.PENDING


    //经过上面这两步,构造方法就变成了:
    constructor(executor) {
        //定义初始化状态
        this.state = MyPromise.PENDING
        //定义成功回调的参数
        this.value = undefined
        //定义失败回调的参数
        this.reason = undefined

        //定义成功回调方法
        const resolve = (value) => {
            if (this.state !== MyPromise.PENDING) {
                //不是初始值,就终止
                return
            }
            //状态改为成功
            this.state = MyPromise.FULFILLED
            //成功赋值
            this.value = value
        }
        //定义失败回调方法
        const reject = (reason) => {
            if (this.state !== MyPromise.PENDING) {
                //不是初始值,就终止
                return
            }
            //状态改为失败
            this.state = MyPromise.REJECTED
            //失败赋值
            this.reason = reason
        }

        try {
            //执行异步方法。这才是关键点
            //前面new的时候只是把方法传递进来了,并没有执行。
            //也就是说,你在使用的时候,把你的网络请求方法传递进来了,如果没有这一步,你的网络请求永远不会执行
            //执行之后,把成功回调,和失败回调暴露给你,你根据实际情况调用。
            //Promise的状态都是封装好的,你也不用关心
            executor(resolve, reject)
        } catch (err) {
            reject(err)
        }
    }
}

  这个时候,就会有大聪明了,这么简单,试一下

new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve(aaa)
    }, 1000);
})

  写到这里,你就会发现,我resolve了之后,没人去接收aaa这个值啊,官方的promise都是用then方法去接收的。
  没错,接下来,我们来实现这个then方法。我们上一篇说过,then方法有两个参数,一个是onFulfilled(成功回调),一个是onRejected(失败回调)

then(onFulfilled, onRejected) {
        //1.处理简单的边界情况,非函数
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err; };
        //2.then方法的返回值还是一个promise,所以这里就要定义一个promise
        const promise2 = new MyPromise((resolve, reject) => {

        })
        return promise2
    }

  接下来就是重点了,到底要怎么触发这个逻辑呢?

//我们都知道,一个回调对应一个状态,成功回调对应成功状态。所以
const promise2 = new MyPromise((resolve, reject) => {
            //成功执行的函数
            const handleFulfilled = () => {

            }
            //失败执行的函数
            const handleRejected = () => {

            }
            if (this.state === MyPromise.FULFILLED) {
                handleFulfilled()
            } else if (this.state === MyPromise.REJECTED) {
                handleRejected()
            } else {

            }
        })

所以,代码就变成上面这个样子了。我差一个题外话
多个then
如上面这个图,同一个promise,是可以调用多个then方法的。每一个then方法都有成功失败的回调。所以,这里需要一个数组去接收。数组放在哪呢?放在构造方法里面初始化。构造方法就变成了如下这个样子了

	...//省略其他代码
	state = MyPromise.PENDING
    //成功回调数组
    onFulfilledCallbacks = []
    //失败回调数组
    onRejectedcallbacks = []

    constructor(executor) {
        //定义成功回调方法
        const resolve = (value) => {
           ...
            //成功赋值
            this.value = value
            //执行所有成功回调
            this.onFulfilledCallbacks.forEach(fn => fn())
        }
        //定义失败回调方法
        const reject = (reason) => {
            ...
            //失败赋值
            this.reason = reason
            //执行所有失败回调
            this.onRejectedcallbacks.forEach(fn => fn())
        }
		...//省略其他代码
    }

然后,我们的then方法就变成了如下这个样子

then(onFulfilled, onRejected) {
        //1.处理简单的边界情况,非函数
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err; };
        //2.then方法的返回值还是一个promise,所以这里就要定义一个promise
        const promise2 = new MyPromise((resolve, reject) => {
            //成功执行的函数
            const handleFulfilled = () => {

            }
            //失败执行的函数
            const handleRejected = () => {

            }
            //根据当前状态执行
            if (this.state === MyPromise.FULFILLED) {
                handleFulfilled()
            } else if (this.state === MyPromise.REJECTED) {
                handleRejected()
            } else {
                // pending 状态,存入队列
                this.onFulfilledCallbacks.push(handleFulfilled)
                this.onRejectedcallbacks.push(handleRejected)
            }
        })
        return promise2
    }

到这里,我们也只是打好了基础,还是没有写实际执行的代码。也就是我们成功执行的函数和失败执行的函数,那么到底要怎么实现呢?

//成功执行的函数
            const handleFulfilled = () => {
                // 模拟微任务(此处用 setTimeout 简化,实际浏览器用 queueMicrotask)
                setTimeout(() => {
                    try {
                        //执行当前then方法的成功回调
                        const x = onFulfilled(this.value)
                    } catch (error) {
                        //执行出错,执行新的promise的reject回调
                        reject(error)
                    }

                }, 0);
            }
            //失败执行的函数
            const handleRejected = () => {
                setTimeout(() => {
                    try {
                        //执行当前then方法的失败回调
                        const x = onRejected(this.reason)
                    } catch (error) {
                        //执行出错,执行当前promise的reject回调
                        reject(error)
                    }
                }, 0);
            }

如上代码,处理了当执行then方法回调出错的情况。接下来处理正常的情况

const handleFulfilled = () => {
                ...
                        //执行当前then方法的成功回调
                        const x = onFulfilled(this.value)
                        this.resolvePromise(promise2, x, resolve, reject)
                ...
            }
            //失败执行的函数
            const handleRejected = () => {
                ...
                        //执行当前then方法的失败回调
                        const x = onRejected(this.reason)
                        this.resolvePromise(promise2, x, resolve, reject)
               ...
            }
//辅助函数:处理返回值可能是 Promise 的情况
    resolvePromise(promise2, x, resolve, reject) {
        if (x === promise2) {
            return reject(new TypeError('Chaining cycle detected'));
        }
        if (x instanceof MyPromise) {
            x.then(resolve, reject)
        } else {
            resolve(x)
        }
    }

到这里,主要逻辑都写完了。完整的代码如下:

class MyPromise {

    // new Promise((resolve,reject)=>{

    // })
    //1.构造方法需要传递一个executor,包含两个参数(resolve,reject)
    //这两个参数都是方法,并且,方法接收一个参数,resolve(value),reject(reason)
    //要注意,这两个方法是promise内部定义的,并不是外部传递进来的。
    // constructor(executor) {

    // }
    //2.有三个状态Pending(等待),Fulfilled(成功),Rejected(失败)。
    //并且状态改变不可逆,resolve方法,把状态从pending——>fulfilled
    //reject方法,把状态从pending——>Rejected
    //需要一个成员变量记录当前状态
    static PENDING = "Pending"
    static FULFILLED = "Fulfilled"
    static REJECTED = "Rejected"
    state = MyPromise.PENDING
    //成功回调数组
    onFulfilledCallbacks = []
    //失败回调数组
    onRejectedcallbacks = []


    //经过上面这两步,构造方法就变成了:
    constructor(executor) {
        //定义初始化状态
        this.state = MyPromise.PENDING
        //定义成功回调的参数
        this.value = undefined
        //定义失败回调的参数
        this.reason = undefined

        //定义成功回调方法
        const resolve = (value) => {
            if (this.state !== MyPromise.PENDING) {
                //不是初始值,就终止
                return
            }
            //状态改为成功
            this.state = MyPromise.FULFILLED
            //成功赋值
            this.value = value
            //执行所有成功回调
            this.onFulfilledCallbacks.forEach(fn => fn())
        }
        //定义失败回调方法
        const reject = (reason) => {
            if (this.state !== MyPromise.PENDING) {
                //不是初始值,就终止
                return
            }
            //状态改为失败
            this.state = MyPromise.REJECTED
            //失败赋值
            this.reason = reason
            //执行所有失败回调
            this.onRejectedcallbacks.forEach(fn => fn())
        }


        try {
            //执行异步方法。这才是关键点
            //前面new的时候只是把方法传递进来了,并没有执行。
            //也就是说,你在使用的时候,把你的网络请求方法传递进来了,如果没有这一步,你的网络请求永远不会执行
            //执行之后,把成功回调,和失败回调暴露给你,你根据实际情况调用。
            //Promise的状态都是封装好的,你也不用关心
            executor(resolve, reject)
        } catch (err) {
            reject(err)
        }
    }

    then(onFulfilled, onRejected) {
        //1.处理简单的边界情况,非函数
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err; };
        //2.then方法的返回值还是一个promise,所以这里就要定义一个promise
        const promise2 = new MyPromise((resolve, reject) => {
            //成功执行的函数
            const handleFulfilled = () => {
                // 模拟微任务(此处用 setTimeout 简化,实际浏览器用 queueMicrotask)
                setTimeout(() => {
                    try {
                        //执行当前then方法的成功回调
                        const x = onFulfilled(this.value)
                        this.resolvePromise(promise2, x, resolve, reject)
                    } catch (error) {
                        //执行出错,执行新的promise的reject回调
                        reject(error)
                    }

                }, 0);
            }
            //失败执行的函数
            const handleRejected = () => {
                setTimeout(() => {
                    try {
                        //执行当前then方法的失败回调
                        const x = onRejected(this.reason)
                        this.resolvePromise(promise2, x, resolve, reject)
                    } catch (error) {
                        //执行出错,执行当前promise的reject回调
                        reject(error)
                    }
                }, 0);
            }
            //根据当前状态执行
            if (this.state === MyPromise.FULFILLED) {
                handleFulfilled()
            } else if (this.state === MyPromise.REJECTED) {
                handleRejected()
            } else {
                // pending 状态,存入队列
                this.onFulfilledCallbacks.push(handleFulfilled)
                this.onRejectedcallbacks.push(handleRejected)
            }
        })
        return promise2
    }

    //辅助函数:处理返回值可能是 Promise 的情况
    resolvePromise(promise2, x, resolve, reject) {
        if (x === promise2) {
            return reject(new TypeError('Chaining cycle detected'));
        }
        if (x instanceof MyPromise) {
            x.then(resolve, reject)
        } else {
            resolve(x)
        }
    }
}

测试如下:
简写promise
关键实现解析:
(1)状态管理

  • 通过 state 变量记录当前状态,确保不可逆。
  • resolve 和 reject 只能修改 pending 状态。

(2)异步回调队列

  • 当 Promise 处于 pending 状态时,.then() 的回调被存入队列。
  • 当状态变化时,依次执行队列中的回调。

(3)链式调用

  • 每个 .then() 返回新的 promise2。
  • 使用 resolvePromise 处理回调返回值:
    • 如果返回普通值,直接 resolve(value)。
    • 如果返回 Promise,等待其解决。

(4)错误处理

  • 使用 try/catch 包裹回调执行,捕获同步错误。
  • 异步错误通过返回的 Promise 传递。

五. 与官方 Promise 的区别

  1. ​微任务队列: 官方 Promise 使用微任务(如 queueMicrotask 或 MutationObserver),而此处用 setTimeout 模拟(实际是宏任务)。
  2. 完整功能: 官方实现包含 .catch()、.finally()、静态方法(如 Promise.all)等。
  3. 边界情况: 官方处理了更多边缘情况(如循环引用、多次 resolve 等)。

六. 总结

  • Promise 的核心是 ​状态管理 + ​回调队列 + ​链式调用。
  • 手写实现需注意:
    • 状态不可变
    • 异步回调的执行顺序
    • 链式调用中返回新 Promise
    • 错误冒泡机制

通过实现一个简化版 Promise,你能更深刻理解 JavaScript 异步编程的设计思想。实际开发中,请直接使用原生 Promise 。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

海晨忆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值