手写 Promise

121 阅读3分钟

Promise

Promise 是每一位前端都需要熟练掌握甚至精通的 api,手写能极大地增强我们的理解

首先我们准备一个类

class Promise2{
    constructor(executor) {
        this.status='pending'
        const resolve=()=>{
            if(this.status==='pending'){
                this.status='fulfilled'
            }
        }
        const reject=()=>{
            if(this.status==='pending'){
                this.status='rejected'
            }
        }
        executor(resolve,reject)
    }
}

现在的 Promise 可以执行 executor 并传入方法,接下来添加 then 方法

class Promise2{
    constructor(executor) {
        this.status='pending'
        this.resolveResult=null
        this.rejectResult=null
        const resolve=(data)=>{
            if(this.status==='pending'){
                this.status='fulfilled'
                this.resolveResult=data
            }
        }
        const reject=(reason)=>{
            if(this.status==='pending'){
                this.status='rejected'
                this.rejectResult=reason
            }
        }
        executor(resolve,reject)
    }
    then(onResolved,onRejected){
        if(this.status==='fulfilled'){
            onResolved(this.resolveResult)
        }
        if(this.status==='rejected'){
            onRejected(this.rejectResult)
        }
    }
}

我们添加属性来记录 resolve 或者 reject 传入的值,作为传入 then 的方法的参数,来测验一下吧

const p1=new Promise2((resolve,reject)=>{
    console.log('start')
    resolve('second')
})
p1.then(data=>console.log(data),reason=>console.log(reason))

p1.png 可以看到,执行器函数可以执行,p1 的状态变为 fulfilled,同时记录了 resolve 的值,同时 then 的回调也能打印出 resolve 的值

看起来没问题,我们改为异步任务试一下

const p1=new Promise2((resolve,reject)=>{
    console.log('start')
    setTimeout(()=>{
        resolve('second')
    },1000)
})
p1.then(data=>console.log(data),reason=>console.log(reason))

结果如下

p2.png 我们发现,并没有执行 then 的回调,原因是代码执行到 then 时,p1 的状态依然是 pending,所以 then 不进行任何处理,我们需要处理在 pending 状态的 promise

class Promise2{
    constructor(executor) {
        this.status='pending'
        this.resolveResult=null
        this.rejectResult=null
        // 储存回调,待 resolve 时调用
        this.callbacks=[]
        const resolve=(data)=>{
            if(this.status==='pending'){
                this.status='fulfilled'
                this.resolveResult=data
                // 处理异步的 resolve
                this.callbacks.forEach(item=>item.onResolved(data))
            }
        }
        const reject=(reason)=>{
            if(this.status==='pending'){
                this.status='rejected'
                this.rejectResult=reason
                //处理异步的 reject
                this.callbacks.forEach(item=>item.onRejected(reason))
            }
        }
        executor(resolve,reject)
    }
    then(onResolved,onRejected){
        if(this.status==='fulfilled'){
            onResolved(this.resolveResult)
        }
        if(this.status==='rejected'){
            onRejected(this.rejectResult)
        }
        if(this.status==='pending'){
            this.callbacks.push({onResolved,onRejected})
        }
    }
}

测试结果如下

p3.png 一秒后成功地打印出了 resolve 的值

不过还远没有结束,我们都知道 then 可以链式调用,因为 then 每次都返回一个 promise

每次返回的 promise 的状态取决于回调函数的返回值,若回调返回非 promise,则状态为 resolve

若返回值为 promise,则以此 promise 的状态为返回 promise 的状态,代码如下

then(onResolved,onRejected){
    return new Promise2((resolve,reject)=>{
        if(this.status==='fulfilled'){
            const result= onResolved(this.resolveResult)
            if(result instanceof Promise2){
                result.then(data=>resolve(data),reason=>reject(reason))
            }else{
                resolve(result)
            }
        }
        if(this.status==='rejected'){
            const result= onRejected(this.resolveResult)
            if(result instanceof Promise2){
                result.then(data=>resolve(data),reason=>reject(reason))
            }else{
                resolve(result)
            }
        }
        if(this.status==='pending'){
            this.callbacks.push({onResolved,onRejected})
        }
    })
}

无论执行哪个回调,都要判断回调的返回值,如果 result 为 promise,则用 then 来监视 result 内部的状态,如果 result 内部 resolve,则 result 的状态为 fulfilled,则一定会执行 resolve(data),然后 then 返回的 promise 的状态变为 fulfilled,而且记录了 result 内部 resolve 传出的值,便于再次 then 时回调能够取到对应的值

接下来补全剩下的状态

then(onResolved, onRejected) {
    return new Promise2((resolve, reject) => {
        if (this.status === 'fulfilled') {
            const result = onResolved(this.resolveResult)
            if (result instanceof Promise2) {
                result.then(data => resolve(data), 
                reason => reject(reason))
            } else {
                resolve(result)
            }
        }
        if (this.status === 'rejected') {
            const result = onRejected(this.rejectResult)
            if (result instanceof Promise2) {
                result.then(data => resolve(data), 
                reason => reject(reason))
            } else {
                resolve(result)
            }
        }
        if (this.status === 'pending') {
            this.callbacks.push({
                onResolved: () => {
                    const result = onResolved(this.resolveResult)
                    if (result instanceof Promise2) {
                        result.then(
                            data => resolve(data),
                            reason => reject(reason))
                    } else {
                        resolve(result)
                    }
                },
                onRejected: () => {
                    const result = onResolved(this.rejectResult)
                    if (result instanceof Promise2) {
                        result.then(
                            data => resolve(data),
                            reason => reject(reason))
                    } else {
                        resolve(result)
                    }
                }
            })
        }
    })
}

对于 pending 状态的处理,其实异步的执行和同步无异,我们只需要先保存一会要做的事情,即检查返回值,并根据 result 内部的处理来决定 then 返回的 promise 的状态, 测试一下

new Promise2((resolve, reject) => {
    console.log('start')
    setTimeout(() => {
        resolve('second')
    }, 1000)
}).then(data => {
        console.log(data)
        return new Promise2((resolve, reject) => {
            setTimeout(()=>{
                reject('error')
            },1000)
        })
    }
    , reason => console.log(reason))
.then(data => console.log(data), reason => console.log(reason))

p4.png 可以看到,结果被成功打印出

其实 promise 的工作原理就是值的传递和状态的传递,一旦 resolve 或 reject 执行,值就被记录下来,状态也被改变,在 then 中,如果执行 resolve 的回调,先观察返回值的成功和失败情况,若成功,调用要返回的 promise 的 resolve,其状态被改变,而且值由内部的 resolve 传出,此时状态的值都已经就位,便可以开始无尽的 then 啦

最后贴上完整代码

class Promise2 {
    constructor(executor) {
        this.status = 'pending'
        this.resolveResult = null
        this.rejectResult = null
        this.callbacks = []
        const resolve = (data) => {
            if (this.status === 'pending') {
                this.status = 'fulfilled'
                this.resolveResult = data
                this.callbacks.forEach(item => item.onResolved(data))
            }
        }
        const reject = (reason) => {
            if (this.status === 'pending') {
                this.status = 'rejected'
                this.rejectResult = reason
                this.callbacks.forEach(item => item.onRejected(reason))
            }
        }
        executor(resolve, reject)
    }

    then(onResolved, onRejected) {
        return new Promise2((resolve, reject) => {
            if (this.status === 'fulfilled') {
                const result = onResolved(this.resolveResult)
                if (result instanceof Promise2) {
                    result.then(
                        data => resolve(data),
                        reason => reject(reason))
                } else {
                    resolve(result)
                }
            }
            if (this.status === 'rejected') {
                const result = onRejected(this.rejectResult)
                if (result instanceof Promise2) {
                    result.then(
                        data => resolve(data),
                        reason => reject(reason))
                } else {
                    resolve(result)
                }
            }
            if (this.status === 'pending') {
                this.callbacks.push({
                    onResolved: () => {
                        const result = onResolved(this.resolveResult)
                        if (result instanceof Promise2) {
                            result.then(
                                data => resolve(data),
                                reason => reject(reason))
                        } else {
                            resolve(result)
                        }
                    },
                    onRejected: () => {
                        const result = onResolved(this.rejectResult)
                        if (result instanceof Promise2) {
                            result.then(
                                data => resolve(data),
                                reason => reject(reason))
                        } else {
                            resolve(result)
                        }
                    }
                })
            }
        })
    }
}