深入理解Promise之一步步教你手写Promise构造函数

前言

Promise是前端面试中很重要的一个考点。如何真正的了解promise?我们不如自己动手,写一个可以拿来用的Promise.js。

下面是循序递进的写法教学,本文的最后也有完整写法的代码可以借鉴,希望能够帮助大家!

后面如果有机会会写一篇详解promise的博客,欢迎关注!

一,手写教学

1.1 基本结构

想要写一个最初的结构,我们不妨先用一下Promise,然后再进行分析。

        let p = new Promise((resolve, reject) => {
    
    
            resolve('巧克力小猫猿')
        })

        p.then(value => {
    
    
            console.log(value)
        }, reason => {
    
    
            console.log(reason)
        })

如上代码,我们了解到了,Promise构造函数里面传进去的是一个函数,所以我们也为其加一个函数类型的形参:

function Promise(executor) {
    
    
}

上述代码也用到了then方法,但目前我们的结构中还没有then,所以也写一个then,且有两个参数:

Promise.prototype.then = function(onResolved, onRejected) {
    
    
}

以上,基本结构已经写完。

1.2 resolve与reject结构搭建

我们都知道,在使用Promise的时候,内部会传一个执行器(函数),且该函数有两个函数类型的参数:resolve和reject。

因此,我们在写结构的时候也要声明着两个函数,并在内部执行器调用的时候使用它们,代码如下:

function Promise(executor) {
    
    
    //同步调用
    function resolve(data) {
    
    
    }

    function reject(data) {
    
    
    }
    executor(resolve, reject)
}

//先添加一个then
Promise.prototype.then = function(onResolved, onRejected) {
    
    
}

1.3 resolve与reject代码实现

我们先来复习一下,promise对象有两个属性,一个是promiseState,意为promise的状态,默认值为pedding,成功状态为resolved,失败状态为rejected;还有一个是promiseResult,意为promise返回的结果。这些变量既然是内置属性,我们应该先对其进行声明:

    //声明属性,状态promiseState和结果值promiseResult
    this.PromiseState = 'pedding'
    this.promiseResult = null

接着,resolve与reject有两个作用:改变promise状态,返回结果。所以我们可以在resolve与reject两个函数中去操作promise的状态与结果:

    //保存实例对象中的this
    const self = this
    function resolve(data) {
    
    
        //修改状态(promiseState),设置对象结果值(promiseResult)
        self.promiseState = 'resolved'
        self.promiseResult = data
    }

    function reject(data) {
    
    
        self.promiseState = 'rejected'
        self.promiseResult = data
    }
    executor(resolve, reject)

这里为什么用的不是this是self?普通函数中的this都指向window,而这里我们修改的是构造函数中的属性,而不是window的。所以我们把构造函数中的this赋值给了self。注意,构造函数中的this指向的是构造函数

1.4 throw抛出异常改变状态

在promise的使用中,一旦使用throw,则状态为失败,且返回值为throw中传入的。

那么,我们也要在构造函数内部写一个捕获异常的代码,利用try,catch。这里如果有疑问可以去搜索下try,catch的相关博客:

    try{
    
    
        executor(resolve, reject)
    }catch(e) {
    
    
        reject(e)
    }

1.5 promise对象状态只能转换一次

在使用promise时,promise的状态只能转换一次:由pedding转换为resolved,或者由pedding转换为rejected。

所以本节我们来做这个限制:只需要在resolve与reject函数中加一段代码:

        if (self.promiseState !== 'pedding') return

意思是:状态只能是由pedding改变为resolved与rejected的,不能由其他状态变为resolved与rejected。

1.6 then方法进行回调

首先我们来分析一下then的用法,看如下代码:

        let p = new Promise((resolve, reject) => {
    
    
            resolve('OK')
        })

        p.then(value => {
    
    
            console.log(value)
        }, reason => {
    
    
            console.log(reason)
        })

这一段代码,先使用resolve,并在里面传值,告诉promise,状态promiseState为resolved,传入的值promiseResult为ok。接着调用then,如果状态是成功的则调用第一个回调,失败则调用第二个。所以回调函数的调用需要判断条件,判断是成功还是失败,接着,value和reason是promiseResult,所以这两个回调的参数我们也要加上。

以上是思路,代码如下:

Promise.prototype.then = function (onResolved, onRejected) {
    
    
    // 调用回调函数
    if(this.promiseState === 'resolved') {
    
    
        onResolved(this.promiseResult)
    }
    if(this.promiseState === 'rejected') {
    
    
        onRejected(this.promiseResult)
    }
}

1.7 异步任务的回调执行

如果没有书写相关代码,异步编程无法实现,如下代码:

        let p = new Promise((resolve, reject) => {
    
    
            setTimeout(() => {
    
    
            	resole('OK')
            }, 1000)
        })

        p.then(value => {
    
    
            console.log(value)
        }, reason => {
    
    
            console.log(reason)
        })

由于这里有一个异步任务,所以最先执行的是p.then,而此时状态还未改变(pedding状态),所以无法输出,因此,我们需要做一些处理。

做什么处理呢:先思考,我们在什么时候使用then?两种情况,一种是上面同步的任务执行完毕,代码按顺序调用then;另一种就是,上面由异步任务,所以跳过先调用then。我们限制考虑的就是第二种情况,如果是这种情况的话,promise的状态还没有来的及改变(pedding),所以没办法执行代码。

我们希望的是在异步任务完成后再调用代码。那么如何实现这一点?如果调用then时状态为pedding,我们可以先声明一份当前的回调函数在当前的构造函数中:

    //声明一个属性保存回调函数
    this.callback = {
    
    }

接着,在then中对callback进行保存:

    //判断pedding的状态
    if (this.promiseState === 'pedding') {
    
    
        //保存回调函数
        this.callback = {
    
    
            onResolved: onResolved,
            onRejected: onRejected
        }
    }

在resolve函数中,等待状态改变后再调用then,也就是调用callback中对应的onResolved:

    function resolve(data) {
    
    
        //修改状态(promiseState),设置对象结果值(promiseResult)
        //限制更改
        if (self.promiseState !== 'pedding') return
        self.promiseState = 'resolved'
        self.promiseResult = data

        //调用then
        if (self.callback.onResolved) {
    
    
            self.callback.onResolved(data)
        }
    }

同理,rejecte函数中也一样:

    function reject(data) {
    
    
        if (self.promiseState !== 'pedding') return
        self.promiseState = 'rejected'
        self.promiseResult = data

        //调用then
        if (self.callback.onRejected) {
    
    
            self.callback.onRejected(data)
        }
    }

1.8 执行多个回调的实现

promise有一个特点,就是,promise可以串联多个操作任务。promise的then会返回一个新的promise,也就是then的链式调用。通过then的链式调用串联多个同步的异步任务。

目前我们的Promise没办法实现该功能,如果写多个then,前面的只会被覆盖。所以本节要解决的就是这个问题。

解决方法:把callback设置成一个数组,一旦是链式调用则在数组里添加内容:

    this.callbacks = []
    if (this.promiseState === 'pedding') {
    
    
        //保存回调函数
        this.callbacks.push({
    
    
            onResolved: onResolved,
            onRejected: onRejected
        })
    }

调用方式也需要更改:

        self.callbacks.forEach(item => {
    
    
            item.onResolved(data)
        }) 
        self.callbacks.forEach(item => {
    
    
            item.onRejected()
        })

1.9 同步修改状态then方法结果返回

首先我们来回顾一下then方法的返回规律:then方法结果的返回在于回调函数的返回值,如果返回一个非promise的值,则结果是一个字符串;如果是一个promise类型的值,返回的是该promise的结果与状态。

这里我们做一步操作:then返回的是一个promise对象,接我们对该对象的返回值类型进行判断,如果返回的是一个promise对象,则另该promise对象调用then,一直调用到结果不是一个promise对象为止;如果结果不是promise对象则令其调用resolve:

Promise.prototype.then = function (onResolved, onRejected) {
    
    
    return new Promise((resolve, rejecte) => {
    
    
        if (this.promiseState === 'resolved') {
    
    
            let result = onResolved(this.promiseResult)
            //如果result是一个promise的对象
            if(result instanceof Promise) {
    
    
                result.then(v => {
    
    
                    resolve(v);
                }, r=> {
    
    
                    reject(r)
                })
            }else {
    
    
                resolve(result)
            }
        }
        //此处省略了下面的代码

如果要抛出异常,状态会改变为失败:

    return new Promise((resolve, rejecte) => {
    
    
        if (this.promiseState === 'resolved') {
    
    
            try{
    
    
                let result = onResolved(this.promiseResult)
                //如果result是一个promise的对象
                if(result instanceof Promise) {
    
    
                    result.then(v => {
    
    
                        resolve(v);
                    }, r=> {
    
    
                        reject(r)
                    })
                }else {
    
    
                    resolve(result)
                }
            }catch(e) {
    
    
                reject(e)
            }
        }

这一块大家理解可能会有点困难,其实本质上是一个递归,一直在调用,直到返回值不再是promise类型的为止。

1.10 异步修改状态then方法结果返回

如下代码,在使用promise时,参杂异步任务:

        let p = new Promise((resolve, reject) => {
    
    
            setTimeout(() => {
    
    
                resolve('OK')
            }, 1000)
        })

        const res = p.then(value => {
    
    
            console.log(value)
        }, reason => {
    
    
            console.log(reason)
        })

        console.log(res)

利用我们的的代码,还没能来得及实现状态的改变和返回值的改变:
在这里插入图片描述
本节将要解决的就是这个问题。我们的代码对于pedding的判断有一个分支:

        //判断pedding的状态
        if (this.promiseState === 'pedding') {
    
    
            //保存回调函数
            this.callbacks.push({
    
    
                onResolved: onResolved,
                onRejected: onRejected
            })
        }

对于异步任务,如果遇到先调用then的情况,1.7中给过解释:等待状态改变后立刻调用callback内部的函数。

        //判断pedding的状态
        if (this.promiseState === 'pedding') {
    
    
            //保存回调函数
            this.callbacks.push({
    
    
                onResolved: function() {
    
    
                    onResolved(self.PromiseResult)
                },
                onRejected: function() {
    
    
                    onRejected(self.PromiseResult)
                }
            })
        }
        if (this.promiseState === 'pedding') {
    
    
            //保存回调函数
            this.callbacks.push({
    
    
                onResolved: function() {
    
    
                    let result = onResolved(self.PromiseResult)
                    if(result instanceof Promise) {
    
    
                        result.then(v => {
    
    
                            resolve(v);
                        }, r=> {
    
    
                            reject(r)
                        })
                    }else {
    
    
                        resolve(result)
                    }
                },

二,总体代码

以上,把Promise基本上可以实现。还有一些拓展的一些方法还未补充。有兴趣的读者朋友可以自己下来研究。
以下是整体代码:

function Promise(executor) {
    
    
    //声明属性,状态promiseState和结果值promiseResult
    this.promiseState = 'pedding'
    this.promiseResult = null
    //声明一个属性保存回调函数
    this.callbacks = []

    //保存实例对象中的this
    const self = this
    function resolve(data) {
    
    
        //修改状态(promiseState),设置对象结果值(promiseResult)
        //限制更改
        if (self.promiseState !== 'pedding') return
        self.promiseState = 'resolved'
        self.promiseResult = data

        //调用then
        self.callbacks.forEach(item => {
    
    
            item.onResolved(data)
        })   
    }

    function reject(data) {
    
    
        if (self.promiseState !== 'pedding') return
        self.promiseState = 'rejected'
        self.promiseResult = data

        //调用then
        self.callbacks.forEach(item => {
    
    
            item.onRejected()
        })
    }

    //异常处理
    try {
    
    
        executor(resolve, reject)
    } catch (e) {
    
    
        reject(e)
    }
}

//先添加一个then
Promise.prototype.then = function (onResolved, onRejected) {
    
    
    const self = this
    // 调用回调函数
    //返回值是一个promise对象
    return new Promise((resolve, rejecte) => {
    
    
        if (this.promiseState === 'resolved') {
    
    
            try{
    
    
                let result = onResolved(this.promiseResult)
                //如果result是一个promise的对象
                if(result instanceof Promise) {
    
    
                    result.then(v => {
    
    
                        resolve(v);
                    }, r=> {
    
    
                        reject(r)
                    })
                }else {
    
    
                    resolve(result)
                }
            }catch(e) {
    
    
                reject(e)
            }
        }
        if (this.promiseState === 'rejected') {
    
    
            try{
    
    
                let result = onRejected(this.promiseResult)
                //如果result是一个promise的对象
                if(result instanceof Promise) {
    
    
                    result.then(v => {
    
    
                        resolve(v);
                    }, r=> {
    
    
                        reject(r)
                    })
                }else {
    
    
                    resolve(result)
                }
            }catch(e) {
    
    
                reject(e)
            }
        }
        
        //判断pedding的状态
        if (this.promiseState === 'pedding') {
    
    
            //保存回调函数
            this.callbacks.push({
    
    
                onResolved: function() {
    
    
                    let result = onResolved(self.PromiseResult)
                    if(result instanceof Promise) {
    
    
                        result.then(v => {
    
    
                            resolve(v);
                        }, r=> {
    
    
                            reject(r)
                        })
                    }else {
    
    
                        resolve(result)
                    }
                },
                onRejected: function() {
    
    
                    let result = onRejected(self.PromiseResult)
                    if(result instanceof Promise) {
    
    
                        result.then(v => {
    
    
                            resolve(v);
                        }, r=> {
    
    
                            reject(r)
                        })
                    }else {
    
    
                        resolve(result)
                    }
                }
            })
        }
    })

}

后记

感谢阅读,后续有机会会出一篇关于Promise的博文,如果感兴趣可以先关注下,谢谢!

猜你喜欢

转载自blog.csdn.net/zxdznyy/article/details/128767547