如何使用promise是前端程序员必会的知识,但是promise本身实现起来却没有那么容易。本文会手把手带你一点一点实现一个最简单的promise。
MyPromise class基本版
promise这个类最基本的功能就是传入处理器函数executor。根据MDN上的解释:
这个“处理器函数”接受两个函数——resolve 和 reject ——作为其参数。当异步任务顺利完成且返回结果值时,会调用 resolve 函数;而当异步任务失败且返回失败原因(通常是一个错误对象)时,会调用reject 函数。
例如:
const
这段程序在1秒之后会打印出” promise”,并执行resolve函数,参数为0。
MyPromise的定义如下:
class
promise中有三个状态:pending,fulfilled以及rejected。在上面这段程序中,使用this.$state来保存promise的状态。
而resolve和reject的函数定义当中,传入的参数res赋给this.$value,从而实现参数res的传递。
最后,调用executor,并把resolve和reject当做两个参数传递进去。如果这中间出现异常,我们会认为这是操作失败,从而调用reject函数。
这是MyPromise这个类调用的时候最基本的功能,虽然有点绕,但是代码还是很直观。接下来我们创建MyPromise的then功能。
MyPromise的then函数基本版
Then这个方法有两个参数:onFulfilled和onRejected。根据MDN:
它们都是 Function 类型。当Promise状态为 fulfilled时,调用 then 的 onfulfilled 方法,当Promise状态为 rejected时,调用 then 的 onrejected 方法。
为了实现then的两个参数会在fulfilled或者rejected的时候被调用,我们需要一个this.$chained来帮忙存储then当中的onFulfilled和onRejected方法,在状态改变之后调用。
代码如下:
class
我们在上面的promise的基础上执行:
promise
可以看到,程序能在1秒之后正确的打印出来resolved、1st then以及0(就是res的值)。
整个过程是这样的:
首先执行MyPromise的constructor,这其中执行executor函数,定时器开始计时,此时定时器内部的resolve并没有被立即调用。
Constructor执行完毕之后,立即执行promise.then函数。把onFulfilled函数放入this.$chained当中(我并没有定义onRejected函数,这个与onFulfilled函数处理方法类似,不再赘述)。此时,this.$chained这个数组有一个元素:
(
在定时器计时结束的时候,执行定时器中的函数,打印resolved,并执行resolve函数。
在resolve函数内部,依次执行this.$chained内部函数。打印1st then以及0
不过这个简易版本的then有个问题:不能chainable,如果调用
promise
则第二个then调用时候会失败,因为第一个then不返回任何值。chainable是then最重要的功能了。我们继续完善then的功能。
Chainable的then
如何让then可以链式调用呢?MDN上写到:
因为 Promise.prototype.then 和 Promise.prototype.catch 方法返回promise 对象, 所以它们可以被链式调用。
那我们改写一下then函数,让其返回一个promise对象。我们先来看这段代码:
then
我们用这段程序测试一下:
promise
打印结果:
promise
而只有”setTimeout ends”是在第二个1秒之后打印出来的。
我们在分析打印结果为什么不对之前,先看看这一版本的then内部都做了什么:
上面的promise调用chain可以改写成这个样子:
let
比如,在p0.then当中,把含有callback(onFulfilled(res))的一个函数定义(callback这个函数中的resolve是p1的resolve,而不是p0的resolve)push给了p0的this.$chained中(onRejected情况与上面类似,不再赘述)。当p0被resolve的时候,执行p0的this.$chained中的代码,也就是执行callback(onFulfilled(res))。在这其中,先执行onFulfilled(res),把执行结果传递给callback函数,作为p1中resolve的参数。
而p1中resolve这个函数执行的过程中,又把p1的this.$chained(内部含有p2的resolve信息)依次执行,以此类推。
那么,原因就找到了,在执行p1.then中MyPromise的setTimeout之后,还未等p3被resolve,就会执行p2的this.$chained中的内容,因此p2.then的内容会被立即执行,从而res的赋值也是错误的。
Then处理异步返回值
我们需要在constructor的resolve定义中添加三行代码,添加完毕之后resolve如下所示:
const
在
if
当中,如果onFulfilled(res)的返回结果是一个promise,那么它就一定满足:res != null && typeof res.then === 'function',则p2的resolve函数就会返回res.then(resolve, reject)。这里的res就是p3,而p3.then接收的resolve就是p2的resolve(递归调用)。
在p3被resolve的时候,会跳过if (res != null && typeof res.then === 'function')这个条件,从而执行p2.then当中的内容(p2的chain当中的内容)。
好了,这就是一个简易的promise。希望大家能有所收获!
参考代码:https://thecodebarbarian.com/write-your-own-node-js-promise-library-from-scratch.html