系列文章
说在前面的
总所周知,Promise 很大程度上将前端开发者从回调地狱中拯救了出来。而在日常开发中,我们对于 Promise 仅仅是使用,其内部原理对于我们是黑盒的。那么 Promise 到底是怎么实现的?Promise 内部机制到底是怎么样的?今天,本文将带着这些疑问从 0 到 1 实现一个基础的 Promise,帮助我们在了解 Promise 实现原理的基础上,提升我们的编程思维。
那么便开始吧。
实现
首先我们来定义一个简单的 Promise:
可以看到,Promise 只接受一个参数,这里我们将其称之为 executor,而 executor 接收两个参数,分别是 resolve 和 reject。那么可以简单的写出我们的 Promise 的框架:
到这里我们也就可以创建一个自己的 Promise 实例,并且调用 resolve 和 reject 方法了。
接下来,我们都知道,Promise 是有状态的,而 resolve 和 reject 会将 Promise 的状态分别改变成两种不同的状态,以触发不同的响应,那么这里就有三种状态:
- pending
- fulfilled
- rejected
这里尝试 new 一个 Promise 实例,并且通过 resolve 修改其状态:
事实上,这里的代码有一个 bug,我们将 Promise 实例打印出来看看:
状态还是 pending,没有被修改。这是为什么呢?我们将 this 打印出来看看就明白了:
那么解决方案就是调用 resolve 和 reject 的时候将 this 绑定即可,这里可以顺便再添加一下错误处理:
接下来,我们知道,Promise 的状态一旦发生改变,就不能再次改变了,这个也好办:首先能改变 Promise 状态的只有 resolve 和 reject,那么只要在这两个函数的入口出进行判断即可:
大体框架基本差不多了,接下来就是 Promise 的核心功能:then 方法了。老规矩,先写一个正常的 Promise.then 看看:
可以看到,then 同样有两个参数,是两个方法。如果在 Promise 中触发了 resolve,则会触发 then 的第一个参数方法,如果触发了 reject,则会触发第二个参数方法,而这两个方法的参数分别从 resolve 和 reject 处获取。并且,为了保证能够调用而不出错,我们需要将不是方法的参数改为方法:
另外,在程序执行中,是有可能出错的,而 Promise 的原则,则是将所有的错误都放到 reject 结果中处理,那么这里我们可以使用 try/catch 处理:
再往后,就到了 Promise 的一个核心点,then 中的执行是异步的,这里我们可以使用 settimeout 将任务加入任务队列中:
到这里,也许有的同学觉得整个 Promise 已经实现了大半了,而事实上,还差得远。先来看第一个问题:
原生的 Promise,我们在 1s 中之后改变其状态,显然这里的结果是先输出 out,1s 之后输出 resolve。而我们自己的 Promise 则不会有任何输出,因为在 then 中,我们没有处理 pending 状态,而 1s 之后 resolve,显然运行到 then 的时候,Promise 的状态还是 pending,所以不会做任何响应。既然无法立即处理,那么这里的解决方案也很简单:
- 创建一个数组用于存储 pending 状态是的操作
- 当 resolve 或 reject 的时候通过遍历数组来处理这些操作
- 错误统一在 reject 结果总处理
- 状态改变触发的方法也异步执行
到这里,一个简单的 Promise 算是基本完成了。不过我们知道,原生的 Promise 支持链式操作。链式操作怎么实现呢?其核心思想是将 self 返回出去。那么这里也类似,我们 new 一个 Promise 实例返回出去即可,至于上一个 then 的值怎么传递到下一个 then 中,我们可以调用上一个 then 的 resolve 和 reject。
同时,原生 Promise 的 then 还存在一个值穿透的特性,即上一个 then 不做任何操作的情况下,resolve 传递的值会穿透到下一个 then 中,这个问题很好解决,在前面我们构建 then 的基础架构的时候,有判断过 then 的两个参数是否为 function,如果不是,则包装成一个 function,这里只需要做一些小小的改动,将值传递到下一个 then 即可:
另外,我们知道,then 中可以返回 Promise,并且在接下来的 then 中接收到的值是 Promise resolve 或者 reject 之后的值,所以这里我们在状态中就要进行判断,如果是 Promise,则进行 resolve 或者 reject 操作,否则直接返回即可。
到这里,我们算是基本实现了一个 Promise,很多同学肯定注意到,前面写的代码中有相当多的冗余部分,俗话说重复代码超过三行就应该优化,这里我们利用享元的思想,将共同部分提取出来进行优化:
接下来我们来看一个原生 Promise 中不太容易被注意到的一个特性:
熟悉 JS 执行流程的同学肯定知道,then 外面先执行,内部后执行,所以这里是无法返回 p2 的,会报错:
这里我们可以在返回前判断,要返回的 promise 是否是执行结果的 promise,如果是则抛出错误:
扩展
上面已经完成了一个基本的 Promise,接下来我们实现一些常用的 Promise 静态方法。
resolve
这个方法将直接改变 Promise 的状态为 fulfilled,并且把值传到 then 中。那么我们可以直接返回一个 promise,通过其 resolve 来改变其状态,而如果获取到的值是一个 Promise,则通过去 then 来改变状态:
reject
显然,这个方法和 resolve 是同样的思路:
all
我们知道 Promise 中的 all 接受一个 Promise 数组,其中所有的 Promise 都成功之后会触发成功方法,只要有一个失败则会触发失败,这里我们可以用一个数组来存储 Promise 们,并且设定一个计数器,当每一个 Promise 成功的时候,计数器加一,当计数器值等于数组的长度的时候,则触发成功,只要有一个失败,则触发失败方法:
race
相比 all,race 要简单得多,只要有一个成功,则触发成功: