初试手写Promise

111 阅读8分钟

Promise

1.Promise的基本框架

  • 先来看原生Promise发生了什么
const promise = new Promise((resolve, reject) => { })
1.Promise是一个构造函数
2.参数为一个函数,且同步自调用了(这里称它为executor)
3.executor中传入了两个函数,分别是resolve和reject,用来改变promise的状态
4.promise的初始状态为pending
5.promise的状态只能修改一次
  • 手写
(function (w) {
    // Promise被new调用时,传入一个函数executor,且executor是自调用的
    function MyPromise(executor) {
        // 需要在promise实例对象上默认添加一个状态为pending
        // 在Promise构造函数中,this就是指向promise实例对象的
        // 这里使用that接收this是为了给resolve和reject函数方便使用
        // 在原生的Promise中,resolve和reject都是直接调用,this为window
        const that = this
        // 初始化promise状态
        that._status = 'pending'
        // resovel函数将promise的状态修改为resolved
        function resolve() {
            // promise的状态只能改变一次,所以需要先判断状态是否为pending
            if (that._status !== 'pending') return
            that._status = 'resolved'
        }
        // reject函数函数将promise的状态修改为rejected
        function reject() {
            if (that._status !== 'pending') return
            that._status = 'rejected'
        }
        // 自调用,并传入两个函数,resolve和reject
        executor(resolve, reject)
    }
    w.MyPromise = MyPromise
}
)(window)
  • 实现
<script>
    console.log(1)
    const promsie = new MyPromise((resolve, reject) => {
        console.log(2)
        resolve()
        reject()
    })
    console.log(3)
    console.log(promsie)
</script>

  • 小结:
    • 数字打印为1 2 3 说明内部的executor函数时同步自调用了
    • promise的状态发生了改变,且只改变了一次

2.promise的then方法的初步实现

  • 原生promise的then方法使用时
promise.then(
    (value) => { },
    (reason) => { }
)
1.then方法是promise实例对象使用的,所以定义在原型上
2.then方法是同步执行的
3.第一个参数是成功的回调,第二个参数是失败的回调
4.回调中可以接收到promise的结果值
  • 手写
(function (w) {
    function MyPromise(executor) {
        const that = this
        // 初始化promise状态和初始值
        that._status = 'pending'
        that._result = undefined

        // 我们需要先创建一个对象用来接收then方法或者catch方法
        that._callback = {}
        function resolve(value) {
            if (that._status !== 'pending') return
            // 改变状态和结果值
            that._status = 'resolved'
            that._result = value
            // 先判断有没有调用then方法,只有调用了then方法,才能给_callback上添加onResolve方法
            // 使用一个定时器来包裹方法,使其成为异步函数
            setTimeout(function () {
                that._callback.onResolve && that._callback.onResolve(value)
            }, 0)
        }
        function reject(reason) {
            if (that._status !== 'pending') return
            // 改变状态和结果值
            that._status = 'rejected'
            that._result = reason
            // 先判断有没有调用then方法,只有调用了then方法,才能给_callback上添加onResolve方法
            setTimeout(function () {
                that._callback.onReject && that._callback.onReject(value)
            }, 0)
        }
        executor(resolve, reject)
    }
    // 在Promise原型上添加then方法,方法中也是两个函数作为参数,分别是成功的回调和失败的回调
    // 这里的this指向的实例对象promise
    MyPromise.prototype.then = function (onResolve, onReject) {
        this._callback.onResolve = onResolve
        this._callback.onReject = onReject
    }
    w.MyPromise = MyPromise
}
)(window)

  • 实现
<script>
    const promsie = new MyPromise((resolve, reject) => {
        resolve(2)
    })
    promsie.then((value) => {
        console.log('success', value)
    }, (reason) => {
        console.log('err', reason)
    }
    )
</script>

  • 小结
    • 当原型添加了then方法时,会将onResolve和onReject通过this传到promise实例对象身上
    • 而在MyPromise构造函数中,通过that(这里替换了this,也是指向promsie实例对象)拿到这两个方法,在resolve和reject中调用
    • onResolve和onReject都是异步函数

3.promise的then方法的基本实现

  • 我们需要实现什么呢

    • 我们在使用then方法的时候是可以链式调用的
    • 每个then方法都会根据不同的情景返回不同的promise对象
  • 我们将then方法修改一下

MyPromise.prototype.then = function (onResolve, onReject) {
    // 调用then方法需要返回一个promise对象
    // 因为promise的状态只能改变一次,所以我们需要返回一个新的promise对象
    // 情景一:当then方法没有返回值,或者有返回值但不是promise对象时,默认返回一个新的成功的promise对象
    // 情景二:当then方法返回一个promise对象需要判断是成功还是失败的peomsie对象
    // 情景三:当then方法调用时,内部报错,需要返回一个失败的peomise对象
    return new MyPromise((resolve, reject) => {
        this._callback.onResolve = function (value) {
            // 可以通过这个方式来获取到调用onResolve方法时的返回值,根据返回值来决定返回出去的promise是什么状态的
            try {
                const result = onResolve(value)
                if (result instanceof MyPromise) {
                    result.then(resolve, reject)
                } else {
                    resolve(result)
                }
            } catch (e) {
                reject(e)
            }
        }
        this._callback.onReject = function (reason) {
            // 可以通过这个方式来获取到调用onReject方法时的返回值,根据返回值来决定返回出去的promise是什么状态的
            try {
                const result = onReject(reason)
                if (result instanceof MyPromise) {
                    result.then(resolve, reject)
                } else {
                    resolve(result)
                }
            } catch (e) {
                reject(e)
            }

        }
    })
}
  • 实现
  • 先试试看捕获成功的
<script>
    const promsie = new MyPromise((resolve, reject) => {
        resolve(2)
    })
    promsie
        .then(
            (value) => {
                console.log('success111',value)
            },
            (reason) => { console.log('err111') }
        )
        .then(
            (value) => { console.log('success222',value) },
            (reason) => { console.log('err111') }
        )
</script>

  • 在试试看捕获失败的
<script>
    const promsie = new MyPromise((resolve, reject) => {
        resolve(2)
    })
    promsie
        .then(
            (value) => {
                console.log('success111', value)
                return new MyPromise((resolve, reject) => {
                    reject(1)
                })
            },
            (reason) => { console.log('err111') }
        )
        .then(
            (value) => { console.log('success222', value) },
            (reason) => { console.log('err111', reason) }
        )
</script>

  • 小结:
    • 这里的注意点是then方法要绑定到实例对象上的两个方法移动到返回的promise中去调用
    • 可以根据两个方法的返回值而去决定new Promise的时候,是调用resolve还是reject

4.promise的catch方法实现

  • 手写
MyPromise.prototype.catch = function (onReject) {
    return this.then(undefined, onReject)
}
  • 小结:
    • catch捕获的是失败状态的promise,所以我们可以借助then方法来实现

5.promise的then和catch一些小细节

  • 情景再现
<script>
    const promsie = new MyPromise((resolve, reject) => {
        resolve(2)
    })
    promsie
        .then(
            (value) => {
                console.log('success111', value)
            }
        )
        .catch(
            (err) => {
                console.log('err111', err)
            }
        )
        .then((value) => {
            console.log('success222', value)
        })
        .catch(
            (err) => {
                console.log('err222', err)
            }
        )
</script>

按我们使用原生的promise来说,这几行代码应该打印的是

//success111 2
//success222 undefined

但实际打印却是这样的

  • 分析一下:

    1. new MyPromise:调用了resolve方法,返回成功状态的promise,结果值为2
    2. 第一个then方法调用:捕获成功状态的promise,执行成功状态的回调,返回一个成功状态的promise 没有返回值
    3. 第一个catch方法调用:但是catch中没有捕获失败的回调,而我们之前写了undefined,所以会出现onRsolve is not a function,会返回一个失败的promise
    4. 第二个then方法没有写第二个参数,所以也是undefined,没法捕获失败的promise,也是返回了一个失败的promise对象,报错内容是onReject is not a function
    5. 第二个catch调用时看到有个失败状态的promise,那正好,我们写了失败的捕获,所以爆出了上面的打印结果
  • 怎么解决:

    1. 当then方法的第二个参数不是个函数的时候,就让它成为函数
    2. 当catch方法的第一个阐述不是个函数的时候,就让它成为函数
    MyPromise.prototype.then = function (onResolve, onReject) {
        //如果then方法接收到失败的promise,且没有第二个参数,那我们就让onReject成为函数,并且将错误发出去,返回一个失败的			peomsie
        onReject = typeof onReject === "function" ? onReject : (value) => { throw value }
        // 如果catch方法接收到成功的promise,且没有第一个参数,那我们就让onResolve成为函数,并且将值传出去,返回一个成功的		 promsie
        onResolve = typeof onResolve === "function" ? onResolve : (value) => value
        return new MyPromise((resolve, reject) => {
            .....
    }
  • 实现
<script>
    const promsie = new MyPromise((resolve, reject) => {
        resolve(2)
    })
    promsie
        .then(
            (value) => {
                console.log('success111', value)
                return 3
            }
        )
        .catch(
            (err) => {
                console.log('err111', err)
            }
        )
        .then((value) => {
            console.log('success222', value)
        })
        .catch(
            (err) => {
                console.log('err222', err)
            }
        )
</script>

<script>
    const promsie = new MyPromise((resolve, reject) => {
        reject(1)
    })
    promsie
        .then(
            (value) => {
                console.log('success111', value)
            }
        )
        .catch(
            (err) => {
                console.log('err111', err)
                throw 333
            }
        )
        .then((value) => {
            console.log('success222', value)
        })
        .catch(
            (err) => {
                console.log('err222', err)
            }
        )
</script>

6.Promise.resolve()

  • 原生的Promise.resolve()实现了什么:

    • 可以传入一个参数
    • 参数不是promise对象,就返回一个成功的promise对象
    • 参数是promise对象,就会将这个promise作为返回值返回出去
    • resolve函数不一定是返回成功的promise对象
  • 手写

MyPromise.resolve = function (value) {
    if (value instanceof MyPromise) {
        return value
    }
    return new MyPromise(resolve => resolve(value))
}
  • 实现
<script>
    const p1 = MyPromise.resolve('111')
    const promsie = new MyPromise((resolve, reject) => {
        reject('222')
    })
    const p2 = MyPromise.resolve(promsie)
    console.log(p1)
    console.log(p2)
</script>

7.Promise.reject()

  • 原生的reject函数
<script>
    const promsie = new Promise((resolve, reject) => {
        reject('222')
    })
    const p = Promise.reject(promsie)
    console.log(p)
</script>

  • 分析

    • 当往reject函数中传入一个promsise对象,无论成功或失败都会报错
    • 可以通过catch捕获到传入的peomise对象(虽然没啥意义)
    • 所以我们可以传入非promise对象的值,官方文档写着让我们传入的参数是Promise被拒绝的原因~
  • 手写

MyPromise.reject = function (value) {
    if (value instanceof MyPromise) {
        throw value
    }
    return new MyPromise((resolve, reject) => reject(value))
}
  • 实现
<script>
    const p1 = MyPromise.reject('1')
    const p2 = MyPromise.reject({})
    console.log(p1)
    console.log(p2)
</script>

8.Promise.all()

  • 先分析all方法需要些什么

    • all方法中传入一个数组,数组由promise对象组成
    • all方法会返回一个promise对象
    • 只有传入的promise对象的状态全部为成功,all方法才会返回一个成功的promise,promise的结果值为所有promise结果值组成的数组
    • 成功的结果值数组里面的每一项顺序都参照传入all方法时,每一项的顺序
    • 当有一个promise状态为rejected的时候,all方法会返回一个失败的promise,错误结果值是这个promsie的错误结果值
    • 返回失败的promise不需要等待全部promise的状态都发生改变,只要一个失败就立马返回
  • 手写

MyPromise.all = function (arr) {
    return new MyPromise((resolve, reject) => {
        // 定义一个数组
        let wrapper = []
        // 定义一个计数器
        let count = 0
        // 遍历传进来的数组
        arr.forEach((item, index) => {
            if (item instanceof MyPromise) {
                item.then(
                    (value) => {
                        // 当有一个promise成功时,就让计数器加一
                        wrapper[index] = value
                        count++
                    	// 计时器等于实参数组的长度时,说明全部的peomise都为成功状态    
                        if (count === arr.length) {
                            resolve(wrapper)
                        }
                    },
                    // 如果有promise失败,那么就直接调用失败的回调
                    reject
                )
            } else {
                //传入的不是promise,是普通值,也是默认成功的状态,计时器加一
                wrapper[index] = item
                count++
                if (count === arr.length) {
                    resolve(wrapper)
                }
            }
        })
    })
}
  • 实现
<script>
    const p1 = MyPromise.resolve('11')
    const p2 = new MyPromise((resolve) => {
        setTimeout(() => {
            resolve('222')
        }, 2000);
    })
    MyPromise
        .all([p1, p2, 3])
        .then(
            (value) => {
                console.log('success', value)
            },
            (err) => {
                console.log('err', err)
            }
        )
</script>

<script>
    const p1 = MyPromise.reject('11')
    const p2 = new MyPromise((resolve) => {
        setTimeout(() => {
            resolve('222')
        }, 2000);
    })
    MyPromise
        .all([p1, p2, 3])
        .then(
            (value) => {
                console.log('success', value)
            },
            (err) => {
                console.log('err', err)
            }
        )
</script>

  • 小结:
    • all方法传入一个数组,数组中的值不一定是promise
    • 所有的promise状态为成功,就返回成功
    • 只要有一个promise状态为失败,立马返回失败
    • 结果值传出时的顺序和传入时的顺序要一致

8.Promise.allsettled()

  • 先分析allsettled方法需要些什么

    • allsettled方法中也是传入一个数组,数组由promise对象组成
    • all方法会返回一个promise对象
    • 当传入的promise对象的状态全部改变时,allsettled方法会返回一个成功的promise,promise的结果值为数组,数组中是一个个对象,对象有每个promsie的状态和结果值
    • 输出的结果值数组中元素的顺序也是根据传入时数组中元素的顺序来排序的
  • 手写

MyPromise.allSettled = function (arr) {
    return new MyPromise((resolve) => {
        let wrapper = []
        //计数器
        let count = 0
        function fn(item, index) {
            const { _status, _result } = item
            const obj = {
                _status,
                _result
            }
            wrapper[index] = obj
            count++
            if (count === arr.length) {
                resolve(wrapper)
            }
        }
        arr.forEach((item, index) => {
            if (item instanceof MyPromise) {
                item.then(
                    () => {
                        fn(item, index)
                    },
                    () => {
                        fn(item, index)
                    }
                )
            } else {
                const obj = {
                    _status: 'resolved',
                    _result: item
                }
                wrapper[index] = obj
                count++
                if (count === arr.length) {
                    resolve(wrapper)
                }
            }

        })
    })
}
  • 实现
<script>
    const p1 = MyPromise.reject('11')
    const p2 = new MyPromise((resolve) => {
        setTimeout(() => {
            resolve('222')
        }, 2000);
    })
    MyPromise
        .allSettled([p1, p2, 3])
        .then(
            (value) => {
                console.log('success', value)
            },
            (err) => {
                console.log('err', err)
            }
        )
</script>