最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Promise核心功能从原理到实现(再也不怕Promise了)

    正文概述 掘金(贰玖是只猫)   2021-03-20   563

    作为一个内卷了的前端小学生,将慢慢前进一直到小学毕业 Orz。 Promise是我们日常工作或者面试常客,虽然很少有面试官让我们去实现一个 Promise,但是假如我们能够从原理的层次去理解Promise A+规范,那么我们将在面试中无往不利,工作中也能够更正确的使用,游刃有余。 我将从我个人学习的角度去实现一个最核心的Promise,如果有错欢迎大家指正。

    首先我们从使用入手,作为最基础的使用方式。

    let promise = new Promise((resolve,reject) => {
        resolve("success")
    })
    promise.then(value => {}, reason => {})
    

    我们发现:

    • Promise 是一个类,在执行这个类的时候,需要传递一个执行器进去然后执行器会立即执行
    • Promise 中有三种状态 fulfilled rejected pending pending --> fulfilled | pending -->rejected 状态一旦确定就不可更改
    • resolvereject函数式用来更改状态的
    • then方法内部做的事情就是判断状态。如果状态成功调用成功的回调函数。反之亦然。
    • then 回调中的参数, 成功参数是成功的值, 失败参数是失败的原因

    结合这几种最基本的使用方式我们可以从这几部分入手实现一个最基本的Promise

    const [PENDING, FULFILLED, REJECTED] = ['pending', 'fulfilled', 'rejected']
    
    class MyPromise {
        constructor(executor) {
            executor(this.resolve, this.reject)
            
        }
        status = PENDING //状态
        value = undefined // 成功后的值
        reason = undefined // 失败的原因
        resolve = value => {  //箭头函数的原因 将内部的 `this` 指向  promise 对象
            // 如果程序不是等待,阻止程序向下运行
            if (this.status !== PENDING) return
            this.status = FULFILLED
            // 保存成功后的值
            this.value = value
        }
        reject = reason => {
            if (this.status !== PENDING) return
            this.status = REJECTED
            this.reason = reason
        }
        then (successCallback, failCallback) {
            if (this.status === FULFILLED) {
                successCallback(this.value)
            }else if (this.status === REJECTED) {
                failCallback(this.reason)
            }
        }
    }
    
    module.exports = MyPromise
    

    我们测试一下写的 MyPromise 是否符合我们的预期

    const MyPromise = require("./MyPromise")
    let promise = new MyPromise((resolve,reject) => {
        resolve("success")
    })
    promise.then(value => {console.log(value)}, reason => {})
    
    // 输出 success
    

    我们发现结果是符合预期的, 我们的第一步的目的就达到了。 但是我们发现你的这个 MyPromise并没有回调中异步的处理逻辑啊。No Problem 我们继续丰富我们的代码。 由于then中加入了异步就导致无法第一时间 resolve或者 reject,那么这个时候就需要我们将异步的回调存储一份,等到 resolve或者 reject 执行的时候再去处理。

    class MyPromise {
        constructor(executor) {
            executor(this.resolve, this.reject)
            
        }
        status = PENDING //状态
        value = undefined // 成功后的值
        reason = undefined // 失败的原因
        successCallback = undefined //成功的回调
        failCallback = undefined // 失败的回调
        resolve = value => {  //箭头函数的原因 将内部的 `this` 指向  promise 对象
            // 如果程序不是等待,阻止程序向下运行
            if (this.status !== PENDING) return
            this.status = FULFILLED
            // 保存成功后的值
            this.value = value
            this.successCallback && this.successCallback(this.value)
        }
        reject = reason => {
            if (this.status !== PENDING) return
            this.status = REJECTED
            this.reason = reason
            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 {
                this.successCallback = successCallback
                this.failCallback = failCallback
            }
        }
    }
    

    我们再测试一下 then 中加入的异步的情况

    const MyPromise = require("./MyPromise")
    let promise = new MyPromise((resolve,reject) => {
        setTimeout(() => {
            resolve("成功")
        }, 3000)
    })
    promise.then(value => {console.log(value)}, reason => {})
    // 3秒后返回 成功
    

    那么我们还知道 同一个 Promise 对象是可以被多次调用的,所以显而易见的是我们的successCallbackfailCallback是一个数组的存在,每调用一次then都需要往回调数组中push一个值。所以这部分需要我们去稍微做一下改造。

    const [PENDING, FULFILLED, REJECTED] = ['pending', 'fulfilled', 'rejected']
    
    class MyPromise {
        constructor(executor) {
            executor(this.resolve, this.reject)
            
        }
        status = PENDING //状态
        value = undefined // 成功后的值
        reason = undefined // 失败的原因
        successCallback = [] //成功的回调
        failCallback = [] // 失败的回调
        resolve = value => {  //箭头函数的原因 将内部的 `this` 指向  promise 对象
            // 如果程序不是等待,阻止程序向下运行
            if (this.status !== PENDING) return
            this.status = FULFILLED
            // 保存成功后的值
            this.value = value
            while(this.successCallback.length) this.successCallback.shift()(this.value)
        }
        reject = reason => {
            if (this.status !== PENDING) return
            this.status = REJECTED
            this.reason = reason
            while(this.failCallback.length) this.failCallback.shift()(this.reason)
        }
        then ( successCallback, failCallback) {
            if (this.status === FULFILLED) {
                successCallback(this.value)
            }else if (this.status === REJECTED) {
                failCallback(this.reason)
            } else {
                this.successCallback.push(successCallback)
                this.failCallback.push(failCallback)
            }
        }
    }
    
    module.exports = MyPromise
    

    再次测试一下看一下结果

    const MyPromise = require("./MyPromise")
    let promise = new MyPromise((resolve,reject) => {
        setTimeout(() => {
            resolve("成功")
        }, 3000)
    })
    promise.then(value => {console.log(`${value}-1`)}, reason => {})
    promise.then(value => {console.log(`${value}-2`)}, reason => {})
    promise.then(value => {console.log(`${value}-3`)}, reason => {})
    // 成功-1
    // 成功-2
    // 成功-3
    

    这个时候在MyPromise中无论是同步还是异步,都可以去满足我们的使用条件。

    现在重点来了,我们知道Promise链式调用的,这部分再我们以前的文章里提过(《异步编程》),感兴趣的同学可以去看一下。其中最重要的一点是**Promisethen的返回都是一个全新的Promise对象**, 结合着这几点我们再去完善一下我们的实现。

    const [PENDING, FULFILLED, REJECTED] = ['pending', 'fulfilled', 'rejected']
    
    class MyPromise {
        constructor(executor) {
            executor(this.resolve, this.reject)
    
        }
        status = PENDING //状态
        value = undefined // 成功后的值
        reason = undefined // 失败的原因
        successCallback = [] //成功的回调
        failCallback = [] // 失败的回调
        resolve = value => {  //箭头函数的原因 将内部的 `this` 指向  promise 对象
            // 如果程序不是等待,阻止程序向下运行
            if (this.status !== PENDING) return
            this.status = FULFILLED
            // 保存成功后的值
            this.value = value
            while (this.successCallback.length) this.successCallback.shift()(this.value)
        }
        reject = reason => {
            if (this.status !== PENDING) return
            this.status = REJECTED
            this.reason = reason
            while (this.failCallback.length) this.failCallback.shift()(this.reason)
        }
        then(successCallback, failCallback) {
            let promise2 = new MyPromise((resolve, reject) => {
                if (this.status === FULFILLED) {
                    setTimeout(() => {  //假如setTimeout的原因是造成异步, 因为我们本身代码是在new Promise的过程中,promise 还不是一个完整的promise对象
                        let x = successCallback(this.value)
                        // 判断 x 的值是普通值还是promise对象
                        // 如果是普通值 直接用resolve传递下去
                        // 如果是promise对象,需要先查看一下是不是跟本身的promise对象相同,如果是那么需要抛出类型错误
                        // 如果不同  就去查看 新promise对象的返回结果
                        // 根据结果, 决定调用 resolve 还是 reject
                        resolvePromise(promise2, x, resolve, reject) //将上个then返回值传递给下个then, 假如是普通值可以直接传递给下个then
                    }, 0)
                } else if (this.status === REJECTED) {
                    failCallback(this.reason)
                } else {
                    this.successCallback.push(successCallback)
                    this.failCallback.push(failCallback)
                }
            })
            return promise2
        }
    }
    
    function resolvePromise(promise2, x, resolve, reject) {
        if (promise2 === x) {
            return reject(new TypeError("Chaining cycle detected for Promise #<Promise>"))
        }
        if (x instanceof MyPromise) {
            // promise 对象
            // x.then(value => resolve(x), reason => reject(reason))
            x.then(resolve, reject) //等同上面
        } else {
            resolve(x)
        }
    }
    
    
    module.exports = MyPromise
    

    这次我们发现,多了一个resolvePromise的函数,这个函数的根本目的是为了根据不同的返回值,区分不同的处理情况,很显然如果是个值我们可以直接resolve出去;如果是个promise对象就需要我们去根据不同的返回调用不同的方法。 除此之外,我们仍然需要注意的一点是,因为then返回的是一个新的Promise对象,这就需要我们去单独做一层检测。这个检测中setTimeout()是一个关键,由于我们的then的返回逻辑是写在Promise对象的立即执行的执行器中,还处在Promise对象的生成阶段,当我们resolvePromise 传入promise2的时候,并不是我们想得到的Promise对象,需要我们加入一个异步,去等待整个实例化的执行结果,从而去检测是否存在循环调用。循环调用的报错是类型报错,因此需要用TypeError去做错误的实例化。 我们执行以下看一下是否符合我们的预期

    const MyPromise = require("./MyPromise")
    let promise = new MyPromise((resolve,reject) => {
        // setTimeout(() => {
            resolve("成功")
        // }, 3000)
    })
    
    let p1 = promise
        .then(value => {
            console.log(value)
            return p1
        })
        
      p1.then(value => console.log(value), reason => console.log(reason.message))
    // 成功
    // Chaining cycle detected for Promise #<Promise>
    

    很显然链式调用可行, 并且能够检测出循环调用。NICE!

    我们在之前的实现过程中,只处理了 状态为 fulfilled 的链式调用的相关逻辑,现在我们补充一下 rejected 以及 pending 的关于链式调用的代码。 另外,因为then方法还有一个特性,如果没有then方法没有参数,那么它会将参数一直向后传递,知道碰到一个有参数的then方法为止。大概意思就是

    let promise = new MyPromise((resolve,reject) => {
        resolve("成功")
    })
    promise.then().then().then(value => console.log(value))
    // 成功
    

    那么我补充一下这部分的实现

    const [PENDING, FULFILLED, REJECTED] = ['pending', 'fulfilled', 'rejected']
    
    class MyPromise {
        constructor(executor) {
            try {
                executor(this.resolve, this.reject)
            } catch (error) {
                this.reject(error)
            }
        }
        status = PENDING //状态
        value = undefined // 成功后的值
        reason = undefined // 失败的原因
        successCallback = [] //成功的回调
        failCallback = [] // 失败的回调
        resolve = value => {  //箭头函数的原因 将内部的 `this` 指向  promise 对象
            // 如果程序不是等待,阻止程序向下运行
            if (this.status !== PENDING) return
            this.status = FULFILLED
            // 保存成功后的值
            this.value = value
            while (this.successCallback.length) this.successCallback.shift()()
        }
        reject = reason => {
            if (this.status !== PENDING) return
            this.status = REJECTED
            this.reason = reason
            while (this.failCallback.length) this.failCallback.shift()()
        }
        then(successCallback, failCallback) {
            successCallback = successCallback ? successCallback : value => value   //返回一个 `function`   (value) => return value
            failCallback = failCallback ? failCallback : reason =>  {throw reason}
            let promise2 = new MyPromise((resolve, reject) => {
                if (this.status === FULFILLED) {
                    setTimeout(() => {  //假如setTimeout的原因是造成异步, 因为我们本身代码是在new Promise的过程中,promise 还不是一个完整的promise对象
                        try {
                            let x = successCallback(this.value)
                            // 判断 x 的值是普通值还是promise对象
                            // 如果是普通值 直接用resolve传递下去
                            // 如果是promise对象,需要先查看一下是不是跟本身的promise对象相同,如果是那么需要抛出类型错误
                            // 如果不同  就去查看 新promise对象的返回结果
                            // 根据结果, 决定调用 resolve 还是 reject
                            resolvePromise(promise2, x, resolve, reject) //将上个then返回值传递给下个then, 假如是普通值可以直接传递给下个then
                        } catch (error) {
                            reject(err)
                        }
                    }, 0)
    
                } else if (this.status === REJECTED) {
                    setTimeout(() => {
                        try {
                            let x = failCallback(this.value)
                            resolvePromise(promise2, x, resolve, reject)
                        } catch (error) {
                            reject(err)
                        }
                    }, 0)
                } else {
                    this.successCallback.push(() => {
                        setTimeout(() => {
                            try {
                                let x = successCallback(this.value)
                                resolvePromise(promise2, x, resolve, reject)
                            } catch (error) {
                                reject(err)
                            }
                        }, 0)
                    })
                    this.failCallback.push(() => {
                        setTimeout(() => {
                            try {
                                let x = failCallback(this.value)
                                resolvePromise(promise2, x, resolve, reject)
                            } catch (error) {
                                reject(err)
                            }
                        }, 0)
                    })
                }
            })
            return promise2
        }
    }
    
    function resolvePromise(promise2, x, resolve, reject) {
        if (promise2 === x) {
            return reject(new TypeError("Chaining cycle detected for Promise #<Promise>"))
        }
        if (x instanceof MyPromise) {
            // promise 对象
            // x.then(value => resolve(x), reason => reject(reason))
            x.then(resolve, reject) //等同上面
        } else {
            resolve(x)
        }
    }
    
    module.exports = MyPromise
    

    需要注意的一点是,我们为了达到一直向后传递参数的目的,我们要将参数为空的successCallbackfailCallback伪造一下。

    • successCallback -> value => value
    • failCallback -> reason => throw reason

    截止到这里,我们就想Promise最核心的部分给实现了。


    起源地下载网 » Promise核心功能从原理到实现(再也不怕Promise了)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元