Js异步编程之 Promise 实现原理剖析 - Mini Promsie

494 阅读3分钟

前言

Promise MDN,理论知识点就跳过了,MDN或者图灵社区文档介绍的都很详细,我们今天就来动动小手实践一下如何实现一个自己的 MiniPromise

进入主题

首先来看看一个常规的 Promsie 是怎么使用的 ?

new Promise((resolve,reject) => {
    setTimeout(() => {
       resolve("hellow promise")
    },1000)
}).then(res => {
    console.log(`成功 ${res}`)
},error => {
    console.log(`失败 ${error}`)
})

Promise {<pending>}
VM696:6 成功 hellow promise   // 1s 后打印

接下来我们把它分解一下,然后来动动小手实践实践

  • promise 函数
new Promise((resolve,reject) => {
    resolve("hellow promise")
})
  • 成功与失败回调
.then(res => {
    console.log(`成功 ${res}`)
},error => {
    console.log(`失败 ${error}`)
})

实现一个 Promise 函数

分析 - 当然也可以通过官网介绍了解

  1. 首先通过 new Promise( () => {} ) 可以看出来它是一个构造函数,并且接受一个函数
  2. 通过 (resolve,reject) => {} 可以知道该函数接受两个参数,成功和失败的回调
  3. 当我们 new Promise() 的时候会自动调用传入的参数(即 1 中的函数)

好,简单的分析之后,来代码实践一下

class MyPromise {
    constructor (executor) {
        // 判断一下 executor 的类型,因为要求是一个函数
        if(typeof executor !== "function") {
            console.log(`${executor} 不是一个函数`)
            return 
        }
        console.log('进来了')
        
        // 定义成功和失败的回到函数
        const resolve = () => {}  // 成功操作
        const reject = () => {} // 失败操作
        
        // executor 调用,传入成功和失败的回调函数
        executor(resolve,reject)            
    }
}

测试

new MyPromise(123)
VM1390:5 123 不是一个函数
MyPromise {}

new MyPromise((resolve,reject) => {})
VM1390:8 进来了
MyPromise {}

看起来是那么回事儿哈,接着往下走

分析

new Promise((resolve,reject) => {
    resolve("hellow promise")
    // reject("我错了")
})
  1. 执行 executor 函数,我们会传入 resolve || reject 函数
  2. 那么它们的执行时机是什么 ? 状态 state 分三种 pendding 等待 | fuldilled 成功 | rejected 失败
  3. resolve || reject 函数依赖 state 状态执行,并且接收成功值和失败原因

所以我们需要定义三个变量,分别是 状态 state , 成功值 value , 失败原因 error


class MyPromise {
    constructor (executor) {
        // 判断一下 executor 的类型,因为要求是一个函数
        if(typeof executor !== "function") {
            console.log(`${executor} 不是一个函数`)
            return 
        }
        console.log('进来了')

        this.state = "pendding"   // 状态
        this.value = ""           // 成功值
        this.error = ""           // 失败原因
        
        // 定义成功和失败的回到函数
        const resolve = (value) => {
            if(this.state === "pendding"){
                this.state = "fulfilled"
                this.value = value;
            }
        }  // 成功操作
        const reject = (error) => {
            if(this.state === "pendding"){
                this.state = "rejected"
                this.error == error
            }
        } // 失败操作
        
        // executor 调用,传入成功和失败的回调函数
        executor(resolve,reject)            
    }
}

测试

new MyPromise((resolve,reject) => {resolve("hellow promise")})
VM1894:8 进来了
MyPromise {state: "fulfilled", value: "hellow promise", error: ""}

成功与失败回调

分析

.then(res => {
    console.log(`成功 ${res}`)
},error => {
    console.log(`失败 ${error}`)
})
  1. then 方法接收两个参数分别是成功回调和失败回调 onFulfilled | onRejected
  2. onFulfilled | onRejected 分别接收构造函数的成功值 velue 和失败原因 error
  3. 扩展:如果 then 接收的不是函数怎么办 ?
    constructor (executor) {
        // 判断一下 executor 的类型,因为要求是一个函数
        if(typeof executor !== "function") {
            console.log(`${executor} 不是一个函数`)
            return 
        }
        console.log('进来了')

        this.state = "pendding"
        this.value = ""
        this.reason = ""
        
        // 定义成功和失败的回到函数
        const resolve = (value) => {
            if(this.state === "pendding"){
                this.state = "fulfilled"
                this.value = value;
            }
        }  // 成功操作
        const reject = (error) => {
            if(this.state === "pendding"){
                this.state = "rejected"
                this.reason == error
            }
        } // 失败操作
        
        // executor 调用,传入成功和失败的回调函数
        executor(resolve,reject)            

    }
    
    
    // then
    then (onFulfilled,onRejected) {
        // 类型校验,重要,不是一个函数,则包裹成函数返回, 做个铺垫,方便后续链式调用
        if(typeof onFulfilled !== "function") {
            onFulfilled = function (value) {
                return value
            }
        }
        if(typeof onRejected !== "function") {
            onRejected = function (error) {
               throw error
            }
        }
        // 调用
        if(this.state == "fulfilled") {
            onFulfilled(this.value)
        }
        if(this.state == "rejected") {
            onFulfilled(this.error)
        }
    }
}

测试

new MyPromise((resolve,reject) => {
    resolve("hellow promise")
}).then(
    res => {
        console.log(`then : ${res}`)
    },
    error => {
        console.log(error)
    }
)
VM2672:8 进来了
VM2889:2 then : hellow promise

截止到这里,感觉有那么点儿意思,貌似基本实现了一个 Promise , 然而事实真的是这样嘛 ? 都知道 promise 是 Js 异步编程解决方案,我悄然的写下一段测试代码,结果 emmm ~~

console.log(1)
new MyPromise((resolve,reject) => {
    console.log(2)
    resolve(3)
}).then(res => {
    console.log(res)
})
console.log(4)
VM3481:1 1
VM3481:3 2
VM3481:6 3
VM3481:8 4

打印结果居然是 1,2,3,4 , 这不符合逻辑呀,按照 eventloop 事件循环 promise 的 then 是异步微任务 结果不是应该 1,2,4,3 嘛 ? 回头一看,阿西巴,我哪里写过异步处理 ? 风风火火的优化一版,在执行回调的时候使用 setTimeout 包裹一下

class MyPromise {
    constructor (executor) {
        // 判断一下 executor 的类型,因为要求是一个函数
        if(typeof executor !== "function") {
            console.log(`${executor} 不是一个函数`)
            return 
        }
        console.log('进来了')

        this.state = "pendding"
        this.value = ""
        this.reason = ""
        
        // 定义成功和失败的回到函数
        const resolve = (value) => {
            if(this.state === "pendding"){
                this.state = "fulfilled"
                this.value = value;
            }
        }  // 成功操作
        const reject = (error) => {
            if(this.state === "pendding"){
                this.state = "rejected"
                this.reason == error
            }
        } // 失败操作
        
        // executor 调用,传入成功和失败的回调函数
        executor(resolve,reject)            

    }
    // then
        then (onFulfilled,onRejected) {
            // 类型校验
            if(typeof onFulfilled !== "function") {
                onFulfilled =  function (value) {
                    return value
                }
            }
            if(typeof onRejected !== "function") {
                onRejected =  function (error) {
                   throw error
                }
            }
            // 调用
            if(this.state == "fulfilled") {
                setTimeout(() => {
                    onFulfilled(this.value)
                })
             
            }
            if(this.state == "rejected") {
                setTimeout(() => {
                    onFulfilled(this.error)
                })
            }
             
        }
}

测试

VM3584:1 1
VM3584:3 2
VM3584:8 4
VM3584:6 3

嗯,是这个样子,然后我又默默的按照常规逻辑写一个异步测试,结果再次 emmm ~~

console.log(1)
new MyPromise((resolve,reject) => {
    console.log(2)
    setTimeout(() => {
         resolve(3)
    },1000)
}).then(res => {
    console.log(res)
})
VM3660:1 1
VM3660:3 2

没打印 3 什么逻辑,经过一番调试,发现当代码执行走进MyPromise构造函数执行setTimeout的时候是异步任务,然后直接往下执行走到 then, 此时进入 then 方法, state 状态为 pendding 所以没做任何事儿,因为我上边只针对状态为 fulfilled | rejected 做了处理,索嘎 ~ 风风火火又优化了一把

逻辑

  1. 进入 then 方法,状态为 pendding 时,将成功和失败回调存储起来
  2. 当状态变更为 fulfilled | rejected 时拿到存储的回调函数执行
class MyPromise {
    constructor (executor) {
        // 判断一下 executor 的类型,因为要求是一个函数
        if(typeof executor !== "function") {
            console.log(`${executor} 不是一个函数`)
            return 
        }
        console.log('进来了')

        this.state = "pendding"
        this.value = ""
        this.reason = ""
        this.onFulfilledCallbacks = []  // 成功回到函数存储
        this.onRejectedCallbacks = []   // 失败回调函数存储
        
        // 定义成功和失败的回到函数
        const resolve = (value) => {
            if(this.state === "pendding"){
                this.state = "fulfilled"
                this.value = value;
                this.onFulfilledCallbacks.forEach(fn => fn(value)) // 异步调用
            }
        }  // 成功操作
        const reject = (error) => {
            if(this.state === "pendding"){
                this.state = "rejected"
                this.reason == error
                this.onRejectedCallbacks.forEach(fn => fn(value)) // 异步调用
            }
        } // 失败操作
        
        // executor 调用,传入成功和失败的回调函数
        executor(resolve,reject)            

    }
    // then
        then (onFulfilled,onRejected) {
            // 类型校验
            if(typeof onFulfilled !== "function") {
                onFulfilled =  function (value) {
                    return value
                }
            }
            if(typeof onRejected !== "function") {
                onRejected =  function (error) {
                   throw error
                }
            }
            // 调用
            if(this.state == "fulfilled") {
                setTimeout(() => {
                    onFulfilled(this.value)
                })
             
            }
            if(this.state == "rejected") {
                setTimeout(() => {
                    onFulfilled(this.error)
                })
            }
            // state === "pendding" 存储回调函数, 成功和失败同理
            if(this.state === "pendding"){
                this.onFulfilledCallbacks.push(value => {
                    setTimeout(() => {
                        onFulfilled(value)
                    })
                })
                 this.onRejectedCallbacks.push(value => {
                    setTimeout(() => {
                        onFulfilled(value)
                    })
                })
            }
        }
}


测试

console.log(1)
new MyPromise((resolve,reject) => {
    console.log(2)
    setTimeout(() => {
         resolve(3)
    },1000)
   
}).then(res => {
    console.log(res)
})
console.log(4)

VM4533:1 1
VM4533:3 2
VM4533:11 4
VM4533:9 3

终于 MyPromise 可以了,都知道 Promise 的优势在于它可以链式调用,那么又是怎么实现的呢 ? 思考:要想实现链式调用,就得返回实例本身,可是 Promise 的状态不可逆,且回调只可调用一次,怎么办, 其实它是包裹了一个新的 Promise 实例,且等我下次分解,追加

小小鼓励,大大成长,欢迎点赞