欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

手把手教你实现 Promise的使用方法

程序员文章站 2022-03-06 10:47:32
前言很多 javascript 的初学者都曾感受过被回调地狱支配的恐惧,直至掌握了 promise 语法才算解脱。虽然很多语言都早已内置了 promise ,但是 javascript 中真正将其发扬...

前言

很多 javascript 的初学者都曾感受过被回调地狱支配的恐惧,直至掌握了 promise 语法才算解脱。虽然很多语言都早已内置了 promise ,但是 javascript 中真正将其发扬光大的还是 jquery 1.5 对 $.ajax 的重构,支持了 promise,而且用法也和 jquery 推崇的链式调用不谋而合。后来 es6 出世,大家才开始进入全民 promise 的时代,再后来 es8 又引入了 async 语法,让 javascript 的异步写法更加优雅。

今天我们就一步一步来实现一个 promise,如果你还没有用过 promise,建议先熟悉一下 promise 语法再来阅读本文。

构造函数

在已有的 promise/a+ 规范 中并没有规定 promise 对象从何而来,在 jquery 中通过调用 $.deferred() 得到 promise 对象,es6 中通过实例化 promise 类得到 promise 对象。这里我们使用 es 的语法,构造一个类,通过实例化的方式返回 promise 对象,由于 promise 已经存在,我们暂时给这个类取名为 deferred

构造函数接受一个 callback,调用 callback 的时候需传入 resolve、reject 两个方法。

promise 的状态

promise 一共分为三个状态:

手把手教你实现 Promise的使用方法

 pending :等待中,这是 promise 的初始状态;

手把手教你实现 Promise的使用方法 

fulfilled :已结束,正常调用 resolve 的状态;

手把手教你实现 Promise的使用方法 

 rejected :已拒绝,内部出现错误,或者是调用 reject 之后的状态;

手把手教你实现 Promise的使用方法

我们可以看到 promise 在运行期间有一个状态,存储在 [[promisestate]] 中。下面我们为 deferred 添加一个状态。

这里还有个有意思的事情,早期浏览器的实现中 fulfilled 状态是 resolved,明显与 promise 规范不符。当然,现在已经修复了。

手把手教你实现 Promise的使用方法

内部结果

除开状态,promise 内部还有个结果 [[promiseresult]] ,用来暂存 resolve/reject 接受的值。

手把手教你实现 Promise的使用方法

手把手教你实现 Promise的使用方法

继续在构造函数中添加一个内部结果。

储存回调

使用 promise 的时候,我们一般都会调用 promise 对象的 .then 方法,在 promise 状态转为 fulfilledrejected 的时候,拿到内部结果,然后做后续的处理。所以构造函数中,还需要构造两个数组,用来存储 .then 方法传入的回调。

resolve 与 reject

 修改状态

接下来,我们需要实现 resolve 和 reject 两个方法,这两个方法在被调用的时候,会改变 promise 对象的状态。而且任意一个方法在被调用之后,另外的方法是无法被调用的。

手把手教你实现 Promise的使用方法

此时,控制台只会打印出 fulfilled ,并不会出现 rejected

调用回调

修改完状态后,拿到结果的 promise 一般会调用 then 方法传入的回调。

熟悉 javascript 事件系统的同学应该知道, promise.then 方法中的回调会被放置到微任务队列中,然后异步调用。

手把手教你实现 Promise的使用方法

所以,我们需要将回调的调用放入异步队列,这里我们可以放到 settimeout 中进行延迟调用,虽然不太符合规范,但是将就将就。

then 方法

接下来我们需要实现 then 方法,用过 promise 的同学肯定知道,then 方法是能够继续进行链式调用的,所以 then 必须要返回一个 promise 对象。但是在 promise/a+ 规范中,有明确的规定,then 方法返回的是一个新的 promise 对象,而不是直接返回 this,这一点我们可以通过下面代码验证一下。

手把手教你实现 Promise的使用方法

可以看到 p1 对象和 p2 是两个不同的对象,并且 then 方法返回的 p2 对象也是 promise 的实例。

除此之外,then 方法还需要判断当前状态,如果当前状态不是 pending 状态,则可以直接调用传入的回调,而不用再放入队列进行等待。

现在我们的逻辑已经可以基本跑通,我们先试运行一段代码:

3 秒后,控制台出现如下结果:

手把手教你实现 Promise的使用方法

可以看到,这基本符合我们的预期。

值穿透

如果我们在调用 then 的时候,如果没有传入任何的参数,按照规范,当前 promise 的值是可以透传到下一个 then 方法的。例如,如下代码:

手把手教你实现 Promise的使用方法

在控制台并没有看到任何输出,而切换到 promise 是可以看到正确结果的。

手把手教你实现 Promise的使用方法

要解决这个方法很简单,只需要在 then 调用的时候判断参数是否为一个函数,如果不是则需要给一个默认值。

手把手教你实现 Promise的使用方法

现在我们已经可以拿到正确结果了。

一步之遥

现在我们距离完美实现 then 方法只差一步之遥,那就是我们在调用 then 方法传入的 onresolve/onreject 回调时,还需要判断他们的返回值。如果回调的内部返回的就是一个 promise 对象,我们应该如何处理?或者出现了循环引用,我们又该怎么处理?

前面我们在拿到 onresolve/onreject 的返回值后,直接就调用了 resolve 或者 resolve ,现在我们需要把他们的返回值进行一些处理。

返回值判断

在我们使用 promise 的时候,经常会在 then 方法中返回一个新的 promise,然后把新的 promise 完成后的内部结果再传递给后面的 then 方法。

判断循环引用

如果当前 then 方法回调函数返回值是当前 then 方法产生的新的 promise 对象,则被认为是循环引用,具体案例如下:

手把手教你实现 Promise的使用方法

then 方法返回的新的 promise 对象 p1 ,在回调中被当做返回值,此时会抛出一个异常。因为按照之前的逻辑,代码将会一直困在这一段逻辑里。

手把手教你实现 Promise的使用方法

所以,我们需要提前预防,及时抛出错误。

现在我们再试试在 then 中返回一个新的 promise 对象。

手把手教你实现 Promise的使用方法

上面的结果也是完美符合我们的预期。

catch 方法

catch 方法其实很简单,相当于 then 方法的一个简写。

静态方法

resolve/reject

promise 类还提供了两个静态方法,直接返回状态已经固定的 promise 对象。

all

all 方法接受一个 promise 对象的数组,等数组中所有的 promise 对象的状态变为 fulfilled ,然后返回结果,其结果也是一个数组,数组的每个值对应的是 promise 对象的内部结果。

首先,我们需要先判断传入的参数是否为数组,然后构造一个结果数组以及一个新的 promise 对象。

接下来,我们需要进行一下判断,对每个 promise 对象的 resolve 进行拦截,每次 resolve 都需要将 remaining 减一,直到 remaining 归零。

下面我们通过如下代码,判断逻辑是否正确。按照预期,代码运行后,在 3 秒之后,控制台会打印一个数组 [2, 4, 6]

手把手教你实现 Promise的使用方法

上面的运行结果,基本符合我们的预期。

race

race 方法同样接受一个 promise 对象的数组,但是它只需要有一个 promise 变为 fulfilled 状态就会返回结果。

下面我们将前面验证 all 方法的案例改成 race。按照预期,代码运行后,在 1 秒之后,控制台会打印一个2。

手把手教你实现 Promise的使用方法

上面的运行结果,基本符合我们的预期。

总结

一个简易版的 promise 类就已经实现了,这里还是省略了部分细节,完整代码可以访问 github 。promise 的出现为后期的 async 语法打下了坚实基础,下一篇博客可以好好聊一聊 javascript 的异步编程史,不小心又给自己挖坑了。。。

到此这篇关于手把手教你实现 promise的方法的文章就介绍到这了,更多相关promise语法内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!

相关标签: Promise 语法