原生JS实现Promise

原生JS实现Promise

摘要:本文讲述如何使用原生js实现Promise。先跟着文章的思路来实现,然后尝试自己实现两三次就会很通顺了。

1、创建Promise对象的时候需要使用new 关键字,所以Promise就是一个类,通过new可以创建Promise实例

const p = new Promise((resolve, reject) => {
    resolve('成功啦哈哈哈')
})
class MyPromise {
}

2、new Promise时需要一个函数作为参数,这个函数是立即执行的,我们称之为执行器,所以要在类中使用constructor接收这个执行器,并且立即执行

class MyPromise {
    constructor(executor) {
        executor()
    }
}

3、执行器需要接受两个参数,一个resolve方法,一个reject方法,这两个方法是用来修改Promise对象的状态status的,所以首先,需要维护一个status属性,将这三种状态定义为常量

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
    constructor(executor) {
        executor()
    }
    status = PENDING
}

4、定义resolve方法和reject方法来修改status属性,并且传递给执行器

class MyPromise {
    constructor(executor) {
        executor(this.resolve, this.reject)
    }
    status = PENDING
    resolve() {
        this.status = FULFILLED
    }
    reject() {
        this.status = REJECTED
    }
}

5、我们已经实现了用resolve和reject方法修改对象的状态。Promise对象都有一个then方法,在这个then方法中,会传递两个回调函数,第一个是Promise对象的状态为fulfilled的回调,第二个是Promise对象的状态为rejected的回调。所以在then中我们会根据status属性来决定执行哪一个回调函数。

then(successCallback, failCallback) {
    if (this.status === FULFILLED) {
        successCallback()
    } else if (this.status === REJECTED) {
        failCallback()
    }
}

6、successCallback和failCallback都是需要接收参数的,successCallback接受的参数就是resolve传进来的参数,所以resolve方法需要维护一个value,failCallback接收的参数就是reject传进来的参数,表示失败的原因,所以reject方法需要维护一个reason

value = undefined
reason = undefined
resolve(value) {
    this.status = FULFILLED
    this.value = value
}
reject(reason) {
    this.status = REJECTED
    this.reason = reason
}
then(successCallback, failCallback) {
    if (this.status === FULFILLED) {
        successCallback(this.value)
    } else if (this.status === REJECTED) {
        failCallback(this.reason)
    }
}

7、如果Promise的执行器中的代码是异步的,就意味着代码走到then的时候,resolve方法和reject方法还没执行,此时的status还是pending ,此时需要将successCallback和failCallback保存下来,等到resolve或者reject方法执行之后再执行成功或者失败回调

successCallback = undefined
failCallback = undefined
resolve(value) {
    this.status = FULFILLED
    this.value = value
    if (this.successCallback) {
        this.successCallback(this.value)
    }
}
reject() {
    this.status = REJECTED
    this.reason = reason
    if (this.failCallback) {
        this.failCallback(this.reason)
    }
}
then(successCallback, failCallback) {
    if (this.status === FULFILLED) {
        successCallback(this.value)
    } else if (this.status === REJECTED) {
        failCallback(this.reason)
    } else if (this.status === PENDING) {
        this.successCallback = successCallback
        this.failCallback = failCallback
    }
}

8、Promise对象的状态一旦确定就不能被改变,所以在resolve和reject中判断status,如果不是pending直接return

resolve(value) {
    if (this.status !== PENDING) return
    this.status = FULFILLED
    this.value = value
    if (this.successCallback) {
        this.successCallback(this.value)
    }
}
reject() {
    if (this.status !== PENDING) return
    this.status = REJECTED
    this.reason = reason
    if (this.failCallback) {
        this.failCallback(this.reason)
    }
}

9、到现在已经实现了then方法,并且支持异步调用的情况,测试一下吧,测试代码:

const p = new MyPromise((resolve, reject) => {
    setTimeout(function () {
        resolve(999)
    }, 3000)
	setTimeout(function () {
        reject(999)
    }, 3000)
})
p.then(value => {
    console.log('成功' + value);
}, reason => {
    console.log('失败' + reason);
})

嗷,出现了报错呢。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uC8iqaqp-1677584696285)(https://tcs.teambition.net/storage/312r85e134be88510cdcda2f334f89e4a85e?Signature=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJBcHBJRCI6IjU5Mzc3MGZmODM5NjMyMDAyZTAzNThmMSIsIl9hcHBJZCI6IjU5Mzc3MGZmODM5NjMyMDAyZTAzNThmMSIsIl9vcmdhbml6YXRpb25JZCI6IjVhMTEyNTljNmFkZTcyNDIyNDMyZGNkOCIsImV4cCI6MTY3NzU1MTEzMiwiaWF0IjoxNjc3NTQ3NTMyLCJyZXNvdXJjZSI6Ii9zdG9yYWdlLzMxMnI4NWUxMzRiZTg4NTEwY2RjZGEyZjMzNGY4OWU0YTg1ZSJ9.GHr3ZiJX8McZYj06uLORxjb96FPd3wLbuwYbKPKgM18&download=image.png “”)]

原因是resolve的this指向问题,看一下resolve方法的调用,相当于在executor里传递了两个function,executor中的上下文是当前对象,但是在function中的this指向的是undefined(es6中的class是严格模式,所以指向undifined,如果是非严格模式,指向的是window)

executor(function () {
   console.log(this);
}, function () {

})

所以应该将resolve和reject写成箭头函数的形式

resolve = value => {
    if (this.status !== PENDING) return
    this.status = FULFILLED
    this.value = value
    if (this.successCallback) {
        this.successCallback(this.value)
    }
}
reject = reason => {
    if (this.status !== PENDING) return
    this.status = REJECTED
    this.reason = reason
    if (this.failCallback) {
        this.failCallback(this.reason)
    }
}

10、测试成功,继续往下进行。then是可以实现多次调用的

const p1 = new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve('第一个成功啦')
    }, 1000)
})
p1.then(value => {
    console.log(value);
}, reason => {
    console.log(reason);
})
p1.then(value => {
    console.log(value);
}, reason => {
    console.log(reason);
})

在异步的情况下,这些回调函数需要全部被保存起来,等到resolve或者reject改变状态后依次全部调用。所以this.successCallback和failCallback要写成数组的形式

resolve = value => {
    if (this.status !== PENDING) return
    this.status = FULFILLED
    this.value = value
    while (this.successCallback.length > 0) {
        this.successCallback.shift()(this.value)
    }
}
reject = reason => {
    if (this.status !== PENDING) return
    this.status = REJECTED
    this.reason = reason
    while (this.failCallback.length > 0) {
        this.failCallback.shift()(this.reason)
    }
}
then(successCallback, failCallback) {
    if (this.status === FULFILLED) {
        successCallback(this.value)
    } else if (this.status === REJECTED) {
        failCallback(this.reason)
    } else if (this.status === PENDING) {
        this.successCallback.push(successCallback)
        this.failCallback.push(failCallback)
    }
}

11、Promise对象可以实现链式调用,所以then方法中返回的应该是一个Promise。如果上一个then中返回的不是Promise,需要将返回值传递给下一个then的成功回调,如果上一个then中返回的是Promise,需要将这个Promise的状态和返回值传递给下一个then的回调

(1)首先实现返回一个Promise

then(successCallback, failCallback) {
    const p2 = new MyPromise((resolve, reject) => {
        if (this.status === FULFILLED) {
            successCallback(this.value)
        } else if (this.status === REJECTED) {
            failCallback(this.reason)
        } else if (this.status === PENDING) {
            this.successCallback.push(successCallback)
            this.failCallback.push(failCallback)
        }
    })
    return p2
}

(2)this.status === FULFILLED时,需要先拿到回调函数的返回值,然后判断这个返回值是不是MyPromise类型。如果时MyPromise类型,需要执行它的then方法,根据其状态确定接下来的then调用哪一个回调函数。

if (this.status === FULFILLED) {
    const x = successCallback(this.value)
    if (x instanceof MyPromise) {
        x.then(resolve, reject)
    } else {
        resolve(x)
    }
}
const p = new MyPromise((resolve, reject) => {
    resolve(888)
})
//第一个then将this.value改为888,并且执行成功回调
p.then(value => {
    console.log('第一次成功' + value);
    return new MyPromise((resolve, reject) => {
		//这里会将this.value改成777,将this.status改为fulfilled,但没有执行回调
        resolve(777)
    })
}, reason => {
    console.log('失败' + reason);
}).then(value => {
	//拿到value:777并执行回调
    console.log('第二次成功' + value);
}, reason => {
    console.log('第二次失败' + reason);
})

(3)在this.status === REJECTED和PENDING时,执行的操作是类似的步骤,所以将相同的操作抽离出来:

//解析MyPromise
function resolvePromise(x, resolve, reject) {
    if (x instanceof MyPromise) {
        x.then(resolve, reject)
    } else {
        resolve(x)
    }
}

(4)当状态是pending的时候,之前是直接将回调函数push进相应的数组中,但是现在我们要push一个新的函数,需要更多的操作,这个函数不需要参数,在执行的时候会找执行时的this.value和this.reason

if (this.status === PENDING) {
    this.successCallback.push(() => {
        const x = successCallback(this.value)
        resolvePromise(x, resolve, reject)
    })
    this.failCallback.push(() => {
        const x = failCallback(this.reason)
        resolvePromise(x, resolve, reject)
    })
}

(5)在执行successCallback数组的时候就不需要再传递参数了

resolve = value => {
    if (this.status !== PENDING) return
    this.status = FULFILLED
    this.value = value
    while (this.successCallback.length > 0) {
        this.successCallback.shift()()
    }
}
reject = reason => {
    if (this.status !== PENDING) return
    this.status = REJECTED
    this.reason = reason
    while (this.failCallback.length > 0) {
        this.failCallback.shift()()
    }
}

12、现在我们已经实现了then方法的链式调用,测试一下吧~ 测试代码:

const p = new MyPromise((resolve, reject) => {
    setTimeout(function () {
        resolve(888)
    }, 3000)
})
p.then(value => {
    console.log('第一次成功' + value);
    return new MyPromise((resolve, reject) => {
        setTimeout(function () {
            reject(777)
        }, 1000)
    })
}, reason => {
    console.log('失败' + reason);
}).then(value => {
    console.log('第二次成功' + value);
}, reason => {
    console.log('第二次失败' + reason);
})

13、测试通过啦!不过还有一个小问题,看下面一段代码:

const p1 = new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve('第一个成功啦')
    }, 1000)
})
const p2 = p1.then(value => {
    console.log(value);
    return p2
}, reason => {
    console.log(reason);
})

在then中返回了then的结果,控制台会报错:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EBwO2Fym-1677584696287)(https://tcs.teambition.net/storage/312rd975a185108c6383bf3ada4da2ab76ad?Signature=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJBcHBJRCI6IjU5Mzc3MGZmODM5NjMyMDAyZTAzNThmMSIsIl9hcHBJZCI6IjU5Mzc3MGZmODM5NjMyMDAyZTAzNThmMSIsIl9vcmdhbml6YXRpb25JZCI6IjVhMTEyNTljNmFkZTcyNDIyNDMyZGNkOCIsImV4cCI6MTY3NzU2MDI5MiwiaWF0IjoxNjc3NTU2NjkyLCJyZXNvdXJjZSI6Ii9zdG9yYWdlLzMxMnJkOTc1YTE4NTEwOGM2MzgzYmYzYWRhNGRhMmFiNzZhZCJ9.wFnmgkeeq5lpd7tdgz-_YBgIL2FLPyYo8koFgeCDa8I&download=image.png “”)]

在我们的代码中也需要实现一下这个控制哦

我们在then方法中返回了一个MyPromise对象,并且在这里我们可以拿到成功回调的return值,所以需要判断这两个MyPromise对象是否相等

then(successCallback, failCallback) {
    const p2 = new MyPromise((resolve, reject) => {
        if (this.status === FULFILLED) {
            const x = successCallback(this.value)
            resolvePromise(p2, x, resolve, reject)
        } else if (this.status === REJECTED) {
            const x = failCallback(this.reason)
            resolvePromise(p2, x, resolve, reject)
        } else if (this.status === PENDING) {
            this.successCallback.push(() => {
                const x = successCallback(this.value)
                resolvePromise(p2, x, resolve, reject)
            })
            this.failCallback.push(() => {
                const x = failCallback(this.reason)
                resolvePromise(p2, x, resolve, reject)
            })
        }
    })
    return p2
}

//解析MyPromise
function resolvePromise(p2, x, resolve, reject) {
    if (p2 == x) {
        throw new TypeError('Chaining cycle detected for promise #<Promise>')
        return
    }
    if (x instanceof MyPromise) {
        x.then(resolve, reject)
    } else {
        resolve(x)
    }
}

14、实现Promise的catch方法,catch方法只处理reject的情况,其他的和then方法是一样的,所以直接在catch中调用then方法就可以,并且then和catch都可以支持不传递参数,不传回调函数的时候,相当于要把当前对象的状态向下传递,所以直接返回this

catch(failCallback) {
    return this.then(undefined, failCallback)
}

then(successCallback, failCallback) {
    successCallback = successCallback ? successCallback : value => this
    failCallback = failCallback ? failCallback : reason => this
    ...
}

15、出现报错:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nhKZd6Lz-1677584696288)(https://tcs.teambition.net/storage/312r0885c3f4b3aefe20dd5f864cac6ba5f0?Signature=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJBcHBJRCI6IjU5Mzc3MGZmODM5NjMyMDAyZTAzNThmMSIsIl9hcHBJZCI6IjU5Mzc3MGZmODM5NjMyMDAyZTAzNThmMSIsIl9vcmdhbml6YXRpb25JZCI6IjVhMTEyNTljNmFkZTcyNDIyNDMyZGNkOCIsImV4cCI6MTY3NzU4NDk0OCwiaWF0IjoxNjc3NTgxMzQ4LCJyZXNvdXJjZSI6Ii9zdG9yYWdlLzMxMnIwODg1YzNmNGIzYWVmZTIwZGQ1Zjg2NGNhYzZiYTVmMCJ9.a-SWU6XMXUNL_xjbRdJDp2V3rcKNQ1Gb1qXM2IqT-WE&download=image.png “”)]

原因是我们在new p2的过程中使用了p2,所以then内部对于p2的使用要放在异步代码中

then(successCallback, failCallback) {
    successCallback = successCallback ? successCallback : value => this
    failCallback = failCallback ? failCallback : reason => this
    const p2 = new MyPromise((resolve, reject) => {
        if (this.status === FULFILLED) {
            setTimeout(() => {
                const x = successCallback(this.value)
                resolvePromise(p2, x, resolve, reject)
            })
        } else if (this.status === REJECTED) {
            setTimeout(() => {
                const x = failCallback(this.reason)
                resolvePromise(p2, x, resolve, reject)
            })
        } else if (this.status === PENDING) {
            setTimeout(() => {
                this.successCallback.push(() => {
                    const x = successCallback(this.value)
                    resolvePromise(p2, x, resolve, reject)
                })
                this.failCallback.push(() => {
                    const x = failCallback(this.reason)
                    resolvePromise(p2, x, resolve, reject)
                })
            })
        }
    })
    return p2
}

16、下面我们实现Promise.resolve方法,这是一个类上的静态方法,可以接收普通数据或者Promise对象,如果是普通数据可以转化为Promise对象并且resolve这个数据,如果是Promise对象不做任何操作

static resolve(item) {
    if (item instanceof MyPromise) {
        return item
    } else {
        return new MyPromise((resolve, reject) => {
            resolve(item)
        })
    }
}

17、实现Promise对象的finally方法,finally方法是不管Promise对象的状态是成功还是失败都会调用的。finally不接受参数,不影响状态,返回的数据不会被后边的then捕捉到。所以直接执行,并且返回当前Promise对象

finally(callBack) {
    callBack()
    return this
}

18、实现Promise.all这个静态方法,这个方法接收一个数组作为参数,返回的是一个Primose,并且resolve所有元素处理结果组成的数组,如果元素是普通数据,则处理结果是自身,如果是Promise对象,则处理结果就是resolve的参数,如果有一个元素reject,那么返回的Promise对象的状态就是失败

static all(arr) {
    return new MyPromise((resolve, reject) => {
        let res = [];
        let index = 0;
        function setData(key, value) {
            res[key] = value
            index++
            if (index == arr.length) resolve(res)
        }
        for (let i = 0; i < arr.length; i++) {
            const item = arr[i]
            if (item instanceof MyPromise) {
                item.then(value => {
                    setData(i, value)
                }, reason => {
                    reject(reason)
                })
            } else {
                setData(i, item)
            }
        }
    })
}

到这里大体就实现完毕啦!!只是还有一点没有实现,就是如果Promise对象的状态为失败的时候,如果没有被catch捕捉就会报错,但是如果有catch或者失败回调就不会报错 这个功能各位看官看一看能不能实现

所有代码:

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
    constructor(executor) {
        executor(this.resolve, this.reject)
    }
    status = PENDING
    value = undefined
    reason = undefined
    successCallback = []
    failCallback = []
    resolve = value => {
        if (this.status !== PENDING) return
        this.status = FULFILLED
        this.value = value
        while (this.successCallback.length > 0) {
            this.successCallback.shift()()
        }
    }
    reject = reason => {
        if (this.status !== PENDING) return
        this.status = REJECTED
        this.reason = reason
        while (this.failCallback.length > 0) {
            this.failCallback.shift()()
        }
    }
    then(successCallback, failCallback) {
        successCallback = successCallback ? successCallback : value => this
        failCallback = failCallback ? failCallback : reason => this
        const p2 = new MyPromise((resolve, reject) => {
            if (this.status === FULFILLED) {
                setTimeout(() => {
                    const x = successCallback(this.value)
                    resolvePromise(p2, x, resolve, reject)
                })
            } else if (this.status === REJECTED) {
                setTimeout(() => {
                    const x = failCallback(this.reason)
                    resolvePromise(p2, x, resolve, reject)
                })
            } else if (this.status === PENDING) {
                setTimeout(() => {
                    this.successCallback.push(() => {
                        const x = successCallback(this.value)
                        resolvePromise(p2, x, resolve, reject)
                    })
                    this.failCallback.push(() => {
                        const x = failCallback(this.reason)
                        resolvePromise(p2, x, resolve, reject)
                    })
                })
            }
        })
        return p2
    }
    catch(failCallback) {
        return this.then(undefined, failCallback)
    }
    static resolve(item) {
        if (item instanceof MyPromise) {
            return item
        } else {
            return new MyPromise((resolve, reject) => {
                resolve(item)
            })
        }
    }
    finally(callBack) {
        callBack()
        return this
    }
    static all(arr) {
        return new MyPromise((resolve, reject) => {
            let res = [];
            let index = 0;
            function setData(key, value) {
                res[key] = value
                index++
                if (index == arr.length) resolve(res)
            }
            for (let i = 0; i < arr.length; i++) {
                const item = arr[i]
                if (item instanceof MyPromise) {
                    item.then(value => {
                        setData(i, value)
                    }, reason => {
                        reject(reason)
                    })
                } else {
                    setData(i, item)
                }
            }
        })
    }
}

//解析MyPromise
function resolvePromise(p2, x, resolve, reject) {
    if (p2 == x) {
        throw new TypeError('Chaining cycle detected for promise #<Promise>')
        return
    }
    if (x instanceof MyPromise) {
        x.then(resolve, reject)
    } else {
        resolve(x)
    }
}







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值