手写promise

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

\bullet 前言

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

\bullet 规范或定义

    \ast 术语

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

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

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

        reson:失败的原因,对应rejected

        exception:异常,对应throw

    \ast 三种状态:pending、fulfilled、rejected

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

        fulfilled:成功状态,由resolve触发

        rejected:失败状态,由reject触发

    \ast then

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

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

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

          支持链式调用

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

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

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

    \ast 解析parsePromise

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

            若promise===x,则reject掉

            若x是promise

                    当前是pending状态,等待

                    当前是fulfilled状态,返回该value

                    当前是rejected状态,返回该reson

            若为对象

                    避免报错try...catch

            若为函数

                    修正this指向,并递归解析

            否则

                    返回x

\bullet 实现

    \ast 初始化

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

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

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

        定义status存储当前的promise状态

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

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

(该入参是同步任务)

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

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

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

            示例

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

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

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

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

            示例

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

                                示例

            示例

        \star 注意

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

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

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

                            示例

(不使用queueMicrotask 情况下输出为111-- promise 1 -- 222)

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

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

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

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

        否则直接返回

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

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

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

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

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

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

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

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

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



parsePromise的bug

(这里导致this指向错误)

promise.all的问题

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
禁止转载,如需转载请通过简信或评论联系作者。

推荐阅读更多精彩内容