promise函数自定义封装详解(二)

promise函数自定义封装详解(二)

大家好,在上一篇文章当中我们基本完成了promise构造函数的声明和内部代码的实现,以及promise函数then方法的分析,编写与调用。但代码总体上来说尚待完善,因为我们只考虑到了同步任务下的then方法相关打印,那如果此时exector构造器函数里传的是一个setTimeout函数,而在这个异步任务里我们再去调用resolve或者reject方法,那此时去执行then方法,控制台会出现什么内容呢?

目标

这节课我们需要实现的目标如下:

  • 实现异步任务的成功执行
  • promise构造函数优化
  • then方法返回值优化

setTimeout异步任务

发现问题

开头提到,之前我们都是在同步任务下调用then,此时我们在exector执行器函数内传入异步任务setTimeout,在这个任务内在调用resolve或reject看,来看效果

  // 创建一个promise实例化对象p
        let p = new Promise((resolve, reject) => {
            // 传入异步任务定时器函数
            setTimeout(() => {
                // 调用reject方法
                reject('失败')
            }, 500)

        })

        // 调用promise的then方法
        p.then(value => {
            // 执行p状态成功时的回调
            console.log(value);
        }, reason => {
            // 执行p状态失败时的回调
            console.log(reason);
        })

打印效果:

promise函数自定义封装详解(二)

我们惊奇地发现,控制台没有打印任何结果,这是为什么呢?我们来分析一下这个现象。

问题分析

首先,executor执行器函数本身是个同步任务,但里面的setTimeout是个异步任务,需要我们等到500毫秒到了我们才能执行里面的reject函数。而我们下面的then方法在此时也是个同步任务,它不会等待setTimeout的执行结果出来之后再执行,而是在构造器函数执行完之后,它就立刻执行。

前面我们提到过,调用then方法时,我们到底要执行里面的参数为value的函数,还是参数为reason的函数,得看executor函数内到底是调用resolve还是调用reject,而此时我们的then方法已经立刻调用了,但是迟迟收不到上面定时器传下来的结果,所以then在极其短暂的时间内无法判断到底执行哪一条,所以就都没有执行,因此控制台上没有显示打印结果。

我们可以尝试着在then方法外面套一个时间为600毫秒的定时器,如图

promise函数自定义封装详解(二)

打印效果:

promise函数自定义封装详解(二)

这样一来,我的then是600毫秒时触发,而构造器函数内部的异步任务是500毫秒时触发,因此then完全可以接收到上面传下来的结果并执行相应内容。

新的思路

如果我们只局限于在then方法外面套定时器,可能会导致后面的代码可读性与可移植性降低,那我们又想实现相同的效果,该怎么办呢?此时我们需要在js文件中对异步任务的相关实现进行操作。

还记得我们之前在promise构造函数内声明的callbacks空数组吗?现在我们就需要用到它了。我们这里大概理清一下思路

  1. 首先我们利用if语句判断实例对象的状态此时是否为pending
  2. 如果为pending,我们再执行花括号内相应的代码
  3. 接着我们要向这个callbacks空数组内推入一个对象,这个对象内包含两个函数onResolved和onRejected

具体代码如下

// 在promise函数原型上挂载一个then方法
Promise.prototype.then = function (onResolved, onRejected) {
    // 成功时的then方法的调用
    if (this.PromiseState === 'fulfilled') {
        onResolved(this.PromiseResult)
    }

    // 失败时的then方法的调用
    if (this.PromiseState === 'rejected') {
        onRejected(this.PromiseResult)
    }

    // 状态为pending时then方法的调用
    if (this.PromiseState === 'pending') {
        this.callbacks.push({
            onResolved: onResolved,
            onRejected: onRejected

        })
    }

}

此时我们已经成功写出了状态为pending时所需要执行的代码,注意此时if语句内的this指向的是实例对象。接着我们再去promise构造函数内的两个函数体进行一些完善步骤

   // 成功时的函数
    function resolve(data) {


        // 设置实例成功时的状态和结果值
        self.PromiseState = 'fulfilled'
        self.PromiseResult = data

        // 执行调用函数为resolve的异步任务
        self.callbacks.forEach(item => {
            item.onResolved(data)
        })
    }

    // 失败时的函数
    function reject(data) {


        // 设置实例成功时的状态和结果值
        self.PromiseState = 'rejected'
        self.PromiseResult = data

        // 执行调用函数为reject的异步任务
        self.callbacks.forEach(item => {
            item.onRejected(data)
        })
    }


可以看到我们分别在resolve和reject函数体内加入了执行异步时的代码,我们在这里利用forEach方法对callbacks内的元素进行遍历,如果此时里面有元素(对象),那就把data值传入这个函数,并执行这个函数。

注意 item.onRejected(…)/item.onResolved(…),这里实现了判断为异步任务时的函数执行,而括号()内的data参数是否参与函数体内的运算,得看script模块中执行then方法时我们具体要做什么,也就是说,data不是必然参与运算

这样一来,我们就基本实现了异步任务的相关操作,这里大家可以稍稍理清一下思路,思考一下如何编写,如何实现,如何执行。

优化promise构造函数

我们下面对promise构造函数内的代再进行一些完善,首先看代码

  // 创建一个promise实例化对象
        let p = new Promise((resolve, reject) => {
        
            resolve('成功') 
            reject('失败')
        })

        // 调用promise的then方法
        p.then(value => {
            // 执行p状态成功时的回调
            console.log(value);
        }, reason => {
            // 执行p状态失败时的回调
            console.log(reason);
        })

发现问题

可以看到我们在executor执行器函数内既调用了resolve方法,也调用了reject方法。按照JS内置的promise函数执行结果来看,如果里面不存在异步任务,我们只能执行第一个调用函数,第二个就不能执行,因为promise函数状态只能改变一次。但我们现在打印来看

promise函数自定义封装详解(二)

分析问题

可以看到,控制台只执行了reject(‘失败’)的相应内容。为什么呢?原来是我们虽然首先调用了resolve函数,改变了p的状态和结果值,但是紧接着执行第二行reject函数,在这个过程中我们覆盖了前面resolve的实现结果,准确来说是改变了对象p内PromiseState和PromiseResult原来的指向。因此,后面then方法会判断此时p的状态为reject,所有只执行了参数为reason的函数,所以控制台上只显示了”失败”。

那我们我们如何只改变一次结果呢?很简单,只要在promise构造函数中的resolve函数或者reject函数里面判断上一次的实例p的状态是否已经改变,如果没改变,那就正常往下执行。如果已经改变过,那就直接return出去,不再执行下面的代码,因此代码如下:

代码优化

 // 成功时的函数
    function resolve(data) {
        // 判断此时的p的状态是否已经改变
        if (self.PromiseState !== 'pending') return

        // 设置实例成功时的状态和结果值
        self.PromiseState = 'fulfilled'
        self.PromiseResult = data

        // 执行调用函数为resolve的异步任务
        self.callbacks.forEach(item => {
            item.onResolved(data)
        })
    }

    // 失败时的函数
    function reject(data) {
        // 判断此时的p的状态是否已经改变
        if (self.PromiseState !== 'pending') return


        // 设置实例成功时的状态和结果值
        self.PromiseState = 'rejected'
        self.PromiseResult = data

        // 执行调用函数为reject的异步任务
        self.callbacks.forEach(item => {
            item.onRejected(data)
        })
    }


现在我们再来打印,如图

promise函数自定义封装详解(二)

这样一来,我们就完成了promise构造函数内部的优化,也实现了相应的目标。

then方法返回值优化

发现问题

现在我们来声明一个变量来接收script模块里then方法的返回值并将其打印:

  //设置变量res接收then方法的返回值
        let res = p.then(value => {
            // 执行p状态成功时的回调
            console.log(value);
        }, reason => {
            // 执行p状态失败时的回调
            console.log(reason);
        })
        
        //打印res
        console.log(res);

promise函数自定义封装详解(二)

此时控制台打印的结果为undefined,这是为何呢?

分析问题

原来我们在js文件中,对于then方法并没有设置任何返回值,而仅仅是执行了then方法相关调用,为了和js内置promise函数then方法的返回值也为promise保持一致,我们应该对then方法进行某些新优化,那具体是什么呢?

此时我们必要要让我们的then方法也得返回一个promise对象,那该怎么操作呢?很简单,我们让then方法里的整体再次返回一个新的promise,来看代码

代码优化

Promise.prototype.then = function (onResolved, onRejected) {
    return new Promise((resolve, reject) => {
        // 成功时的then方法的调用
        if (this.PromiseState === 'fulfilled') {
            onResolved(this.PromiseResult)
        }

        // 失败时的then方法的调用
        if (this.PromiseState === 'rejected') {
            onRejected(this.PromiseResult)
        }

        // 状态为pending时then方法的调用
        if (this.PromiseState === 'pending') {
            this.callbacks.push({
                onResolved: onResolved,
                onRejected: onRejected

            })
        }
    })

}

此时我们再来打印结果,如图

promise函数自定义封装详解(二)

这时候我们成功的让then方法执行的返回值为一个promise对象,可以看到这时候新对象的PromiseState和PromiseResult的值的都为默认值,这是因为新promise对象的exector执行器函数传参到上面的构造函数promise中,重新接受了默认的结果和状态。

抛错测试

我们在这里可以尝试一下在script模块里的then方法里抛错,看看我们之前在promise构造函数里利用try..catch..是否捕捉到了异常,并打印最终的结果

promise函数自定义封装详解(二)

打印结果:

promise函数自定义封装详解(二)

可以看到我们的抛错测试成功,这里注意一点,一般当我们检测到throw 抛错并且执行时,我们的catch就会捕获这个错误,throw后面的函数就不会执行了

总结

这节课我们成功实现了异步任务的执行,以及promise构造函数优化和then方法返回值的完善,在后续的文章当中我们将要持续优化then方法内部的代码结构,以及添加promise的几种内置API,今天先到这里,谢谢大家!

本节代码

html代码

   // 创建一个promise实例化对象
        let p = new Promise((resolve, reject) => {


            // //  setTimeout 异步执行
            // setTimeout(() => {
            //     resolve('成功')
            // })

            resolve('成功')
            // reject('失败')

        })

        // 调用promise的then方法
        let res = p.then(value => {
            // 执行p状态成功时的回调
            console.log(value);
            
            //抛错测试
            //throw 'Error'
        }, reason => {
            // 执行p状态失败时的回调
            console.log(reason);
        })
        console.log(res);



js文件代码

// 定义promise构造函数
function Promise(executor) {
    // 设置默认状态和默认值
    this.PromiseState = 'pending'
    this.PromiseResult = null

    // 设置一个空数组,异步时调用
    this.callbacks = []

    // this赋给self
    const self = this

    // 成功时的函数
    function resolve(data) {
        // 判断此时的p的状态是否已经改变
        if (self.PromiseState !== 'pending') return

        // 设置实例成功时的状态和结果值
        self.PromiseState = 'fulfilled'
        self.PromiseResult = data

        // 执行调用函数为resolve的异步任务
        self.callbacks.forEach(item => {
            item.onResolved(data)
        })
    }

    // 失败时的函数
    function reject(data) {
        // 判断此时的p的状态是否已经改变
        if (self.PromiseState !== 'pending') return


        // 设置实例成功时的状态和结果值
        self.PromiseState = 'rejected'
        self.PromiseResult = data

        // 执行调用函数为reject的异步任务
        self.callbacks.forEach(item => {
            item.onRejected(data)
        })
    }


    // 执行函数并捕捉可能存在的异常
    try {
        executor(resolve, reject)
    } catch (e) {
        reject(e)
    }
}

// 在promise函数原型上挂载一个then方法
Promise.prototype.then = function (onResolved, onRejected) {
    // 返回一个新的promise对象
    return new Promise((resolve, reject) => {
        // 成功时的then方法的调用
        if (this.PromiseState === 'fulfilled') {
            onResolved(this.PromiseResult)
        }


        // 失败时的then方法的调用
        if (this.PromiseState === 'rejected') {
            onRejected(this.PromiseResult)
        }

        // 状态为pending时then方法的调用
        if (this.PromiseState === 'pending') {
            this.callbacks.push({
                onResolved: onResolved,
                onRejected: onRejected

            })
        }
    })

}