手写promise

    promise是一个基于原型实现的类,可以被链式then调用,是业务中解决异步的常用措施

前言

    关于promise的基础语法,可以参考我两年前写的不成熟小文以及蹩脚的使用示例,promise相关的常见面试题也可以参考这里

规范或定义

    术语

        promise:类实例,通过new获取,接受一个可选的函数类型参数

        thenable:一个可链式调用的函数,用于获取promise的值

        value:成功的返回值,对应resolve函数

        reson:失败的原因,对应rejected

        exception:异常,对应throw

    三种状态:pending、fulfilled、rejected

        pending:初始状态,可通过resolve或rejected修改状态

        fulfilled:成功状态,由resolve触发

        rejected:失败状态,由reject触发

    then

         用于获取上一个promise的值,包括成功和失败

          onFulfilled和onRejected仅在状态改变时被执行一次,且这两个函数应该是微任务(queueMicrotask)

          可被多次注册多次执行,当状态改变时需要按注册的先后顺序执行

          支持链式调用

          回调函数传出的结果应当进行解析保持格式统一

          当回调过程中throw error时,统一适用reject拒绝

           若回调不是函数,应当向上取值

    解析parsePromise

            接受四个参数:promise,x,resolve,reject

            若promise===x,则reject掉

            若x是promise

                    当前是pending状态,等待

                    当前是fulfilled状态,返回该value

                    当前是rejected状态,返回该reson

            若为对象

                    避免报错try...catch

            若为函数

                    修正this指向,并递归解析

            否则

                    返回x

实现

    初始化

        定义三种状态标识:pending、fulfilled、rejected

        定义辅助函数isFunc判断是否传入的是函数,对于传入的onReject或onFulfilled如果不为函数应该直接返回value或reason

        定义成功和失败的返回结果容器value和reason

        定义status存储当前的promise状态

手写promise_第1张图片

    定义resole和reject接口,用于修改promise的状态,该状态仅在status为pending时执行

手写promise_第2张图片

    接收入参,应当立即调用,并将resolve和reject函数抛出以等待合适的时机修改status

手写promise_第3张图片
(该入参是同步任务)

    定义then函数,并作简单的容错处理,若传入的不是函数类型,则包装一个默认函数;为了实现链式调用,在最后返回一个新的MyPromise实例

手写promise_第4张图片

     由于new Promise是同步任务,故存在直接修改状态的情况,即在new Promise的回调中直接调用resolve或reject。那么在.then返回的promise中需要返回上一次的值,即this.value或this.reason

手写promise_第5张图片
(在上一个MyPromise被resolve或reject后调用then,返回的新的MyPromise由于又是同步立即执行,故此时框红位置访问到的是上一次的值)

            示例

手写promise_第6张图片

    同样的,如果第一个new Promise回调时,也可能是执行了异步逻辑如宏任务微任务又或是ajax,此时直接调用.then由于未调用resolve或reject仍为pending,无法获取上一次的结果,需要等待状态改变后再计算结果抛出;且.then可被注册多次,每一级的then都需要等待上一次的then返回的promise的结果。故使用数组先将它们存起来等待调用时机

手写promise_第7张图片
(只有第一个会存在异步,必将库内部不可能主动帮你去生成一个异步代码)

    由于只有第一个会被阻塞,故只需要监听第一个status由pending变为fulfilled或rejected即可

手写promise_第8张图片
(使用私有变量_status是为了避免死循环;框黄区域是之前实现的缺陷,因为需要在 this.FULFILLED_CBS 遍历过程中拿到this.value并传递给下一个.then)

            示例

手写promise_第9张图片

    除了同步和异步,还可能在回调中写了错误的代码,对于同步代码,其在constructor中会被直接执行,而异步逻辑则在监听到status时遍历执行,故异常处理应当有两处

手写promise_第10张图片

                                示例

手写promise_第11张图片

            示例

手写promise_第12张图片

        注意

                必须显示的调用resolve,否则不会触发status的改变,从而.then无法执行

                在setTimeout中书写的错误无法也不应当在类内部捕获

    为了不阻塞代码,需要将resolve和reject的执行修改为微任务

手写promise_第13张图片

                            示例

手写promise_第14张图片
(不使用queueMicrotask 情况下输出为111-- promise 1 -- 222)

    resolve和reject作为函数,允许存在返回值,返回值将透传到下一个then中。需要对返回值作进一步解析

        若返回值是实例本身,则应当报循环引用错误(a返回的直接是实例本身,也可能a返回的是一个Promise,但是该Promise返回的是a)

        若返回值是MyPromise实例,则应该对其解析获取其resolve或reject的值

        若为对象且非null,则尝试取then,并解析其resolve或reject(对上一个递归求值

手写promise_第15张图片

        否则直接返回

手写promise_第16张图片

    对于catch而言,也是返回一个promise以链接调用。且该方法专门用于获取reason。恰好,then方法也是用来获取value和reason的,也恰好返回是新的promise

    原生promise也支持不new的方式创建一个promise实例,对应静态方法resolve和reject

手写promise_第17张图片

    原生的race的表现是,接收多个promise,只要有一个状态先改变了,那么race的结果就是这个已经改变的。需要注意的是:

        传入的值可能不是一个promise,需要调用静态的resolve方法将其转为promise

        需要使用.then进行求值,当上一个promise(使用resolve包装过的)为异步时第二个会放入回调队列中等待

        race也是一个promise,当存在一个先执行完毕时,直接执行race的resolve求值即可

手写promise_第18张图片

    原生的all的表现是,所有的promise均为fulfilled时,返回,否则为rejected

         all的逻辑其实和race有点像,只不过这里是取的rejected的值

        定义数组存储then成功数,全部执行时执行resolve

手写promise_第19张图片



parsePromise的bug

手写promise_第20张图片
(这里导致this指向错误)

promise.all的问题

    直接arr.push会导致数据的非一一对应,可以通过arr[i]的形式来存储每一个异步对应的结果,同时另外使用变量count作为结束条件

你可能感兴趣的:(手写promise)