🎢前端进阶:手写Promise实现(一)链式调用 🚀

141 阅读5分钟

摘要

"本文将从零开始手写实现一个简化版Promise,重点讲解then方法的实现原理和链式调用机制。通过不到100行代码,我们将揭开Promise的神秘面纱,理解其背后的设计思想。虽然暂未实现reject等完整功能,但核心的链式调用机制已完整呈现,为后续扩展打下基础。"

哥几个众所周知

Promise的状态分为三个状态:

  • pending(等待),
  • fulfilled(完成),
  • rejected(拒绝);
    并且状态一旦更改,将不可回滚

Promise核心执行流程:

  1. 状态初始化 :创建Promise实例时,初始状态设为pending
  2. 执行器函数调用 :立即执行传入的executor函数,并将内部resolve/reject方法作为参数传入
  3. 回调注册 :通过then方法注册成功/失败回调(支持多次调用)
  4. 异步执行保证 :确保then方法的回调函数在调用后的新事件循环中执行

链式调用本质 :当前Promise状态变为fulfilled后,会自动触发下一个Promise的执行,形成异步任务链。

链式调用

我们先写出符合 PromiseA+ 规范的Promise乞丐版框架

function ViPromise (fn) {
    let state = 'pending' // 状态
    let value = null
    const callbacks = [] // 回调队列

    function handle () {}
    function resolve () {}
    function reject () {}

    /**
     * 需要返回新的promise实例,但是执行环境属于上一个promise阶段
     * @param {*} onFulfilled 
     * @returns 
     */
    this.then = function (onFulfilled) {
        return new ViPromise ((resolve, reject) => {
            handle({
                onFulfilled,
                resolve
            })
        })
    }

    fn() // 执行用户fn
}

然后补充一下基础逻辑:

const STATUS_ENUM = {
    PENDING: 'pending',
    FULFILLED: 'fulfilled',
    REJECTED: 'rejected'
}
function ViPromise (fn) {
    let state = STATUS_ENUM.PENDING // 状态
    let value = null
    const callbacks = [] // 回调队列

    function handle (callback) {
        if (state === STATUS_ENUM.PENDING) {
            callbacks.push(callback)
            return
        }
        if (state === STATUS_ENUM.FULFILLED) {
            if (!callback.onFulfilled) {
                callback.resolve(value)
                return
            }
            const ret = callback.onFulfilled(value)
            callback.resolve(ret)
        }
    }
    function resolve (newValue) {
        const fn = () => {
            if (state !== STATUS_ENUM.PENDING) return
            // 状态变更
            state = STATUS_ENUM.FULFILLED
            value = newValue
            handleCb()
        }
        setTimeout(fn, 0) // 符合Promise A+规范
    }
    function reject () {}

    function handleCb () {
        while (callbacks.length) {
            const fulfilledFn = callbacks.shift()
            handle(fulfilledFn)
        }
    }
    /**
     * 需要返回新的promise实例,但是执行环境属于上一个promise阶段
     * @param {*} onFulfilled 
     * @returns 
     */
    this.then = function (onFulfilled) {
        return new ViPromise ((resolve, reject) => {
            //桥梁,将then返回的新promise的resolve方法,放到前一个 promise 的回调对象中
            handle({
                onFulfilled,
                resolve
            })
        })
    }

    fn(resolve) // 执行用户fn
}

const test = new ViPromise((resolve, reject) => {
    console.log('1 promise')
    setTimeout(() => {
        resolve({ test: 123 })
    }, 1000)
}).then((res) => {
    console.log(res, 'get result')
    // return { test: 222}
}).then((res) => {
    console.log(res, 'get result 2')
})
console.log(test)

我们直接从测试样例开始观察ViPromise的运行过程

const test = new ViPromise((resolve, reject) => {
    console.log('1 promise')
    resolve({ test: 123 })
}).then((res) => {
    console.log(res, 'get result')
    // return { test: 222}
}).then((res) => {
    console.log(res, 'get result 2')
})

Untitled-2025-04-04-1643.png

小结:整个流程做的事情其实比较容易看出来,关键的步骤就是:

  • 在new Promise的时候把控制他状态成功与否的resolve方法传递给用户
  • 同时.then方法完成注册(callbacks.push),这个时候then方法会返回一个新的Promise对象
  • 而then中的用户cb执行的时机,就在于new Promise中用户什么时候调用resolve方法更改Promise 1的状态,并且把方法返回的结果当作value,传给下一个promise,并且这个时候就会去执行callbacks队列(then中的cb就会被执行)

然后继续执行cb,类推,链式调用的效应就出来了

一般我们使用Promise处理回调嵌套一般是
案例1:

const test = new ViPromise((resolve, reject) => {
    console.log('1 promise')
    setTimeout(() => {
        resolve({ test: 123 })
    }, 1000)
}).then((res) => {
    console.log(res, 'get result')
    return fetchData()
}).then((res) => {
    console.log(res, 'get result 2')
})
const fetchData = (id) => {
    return new ViPromise((resolve) => {
        setTimeout(() => {
            resolve({ name: '鸡腿', id })
        }, 5000)
    })
}

打印结果:

image.png

这肯定是不理想的,我们希望fetchData的异步操作能得到处理,而观察到异步处理的返回结果(ViPromise)会再次传入resolve方法,得到的打印结果如下:

image.png

image.png

所以我们需要在resolve里增加对返回的ViPromise的处理逻辑:

function resolve (newValue) {
    const fn = () => {
        if (state !== STATUS_ENUM.PENDING) return
        if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
            const { then: customThenFn } = newValue
            if (typeof customThenFn === 'function') {
                console.log(newValue, 'catch promise new Value')
                customThenFn.call(newValue, resolve)
                return // 中止状态变更,需要等待中间的ViPromise完成后进行变化(那就把当前的resolve环境上下文给到新的Promise)
            }
        }
        // 状态变更
        state = STATUS_ENUM.FULFILLED
        value = newValue
        handleCb()
    }
    setTimeout(fn, 0) // 符合Promise A+规范
}

我们描述一下这个过程的ViPromise的状态变更流程:

ViPromisecallback
p1[{ onFulfilled: then1(第一个then里面的fn), resolve: p1->Fulfilled,p2resolve}]
p2(p1的then产生)[{ onFulfilled: then2(第二个then里面的fn), resolve: p2->Fulfilled,p3resolve}]
p3(p2的then产生)[]
p4(执行then1的fetchData产生)[{ onFulFilled: 手动注入的p2的resolve方法, resolve: p5resolve }]
p5(手动then方法后生成)[]

image.png

如果then方法(声明为P2的then方法)的cb中,用户返回的是一个新的ViPromise对象-P4,那么我们就要做特殊处理,中止后续的P2状态变更为Fulfilled,因为我们需要等待外部的ViPromise执行完毕,外部的ViPromise对象中存在then方法,当外部异步完成后,需要调用resolve方法使P4状态-Fulfilled,之后就会运行P4的then方法中的回调,就可以让P2状态继续变更为Fulfilled;
但是外部直接return,存在then方法,但是没有调用then方法,我们就需要手动注入调用逻辑:

customThenFn.call(newValue, resolve)
                

这样,当外部的P4的then方法执行后,就会调整P2的状态为Fulfilled,然后继续运行P3的then方法中的回调

调整之后的打印:

image.png

至此,我们完成了一个简易版Promise-链式调用的demo实现