最近比较闲,忙里偷闲看了下Promise/A+标准,参考了一些文章手动实现了一个Promise。为了加深印象,在这里做一个总结。也供大家参考
什么是Promise
Promise是一种异步解决方案,比起传统的回调更加的方便与强大。避免了多重异步函数嵌套产生所谓的回调地狱。ES6将其写进了语言标准,统一了语法。这篇文章不会过多的讲解Promise的语法和特性(若不了解可以先查看阮一峰老师的Promise教程),而会专注于Promise底层的实现。实现一个符合Promise/A+规范的promise demo.
在这里简单创建一个Promise实例
1 | let promise = new Promise((reslove,reject)=>{ |
上面是一个Promise 实例,输出内容this is a promise demo 然后一秒后输出success 可以看出,promise 很好的解决了异步的问题。
Promise/A+ 规范
Promise/A+规范扩展了早期的Promise/A proposal提案,我们来解读一下Promise/A+规范。
术语
promise:promise 是一个拥有
then
方法的对象或函数,其行为符合本规范;thenable:是一个定义了
then
方法的对象或函数,文中译作“拥有then
方法”;value:指任何 JavaScript 的合法值(包括
undefined
, thenable 和 promise)exception:是使用throw抛出的一个异常
reason:表示一个 promise 的拒绝原因。
要求
promise的状态:一个 Promise 的当前状态必须为以下三种状态中的一种:等待态(Pending)、执行态(Fulfilled)和拒绝态(Rejected)。
等待态(pending):
处于等待态时,promise 需满足以下条件:
- 可以迁移至执行态或拒绝态
执行态 (Fulfilling)
处于执行态时,promise 需满足以下条件:
- 不能迁移至其他任何状态
- 必须拥有一个不可变的终值
拒绝态(Rejected)
处于拒绝态时,promise 需满足以下条件:
不能迁移至其他任何状态
必须拥有一个不可变的据因
Then方法
一个 promise 必须提供一个
then
方法以访问其当前值、终值和据因。promise 的
then
方法接受两个参数:
1 | promise.then(onFulfilled, onRejected) |
then方法的返回值必须是一个Promise
其他
其他规范就不再过多的阐述了,若想详细了解可以查看图灵社区的Promise/A+规范
Promise的初步实现
从Promose/A+ 规范和日常使用中我们可以知道下面几点。
- Promise 是一个类,当执行
new Promise()
时会返回一个Promise实例 - 每个Promise实例上都有一个then方法,参数分别为执行成功/执行失败时的回调。当Promise实例从等待态改变为其他状态时,执行相应的回调函数。
一个 Promise 的当前状态必须为以下三种状态中的一种:等待态(Pending)、执行态(Fulfilled)和拒绝态(Rejected)。
Promise的默认状态是等待态,可以改变为执行态和拒绝态。当状态改变后不会再改变为其他状态
写到这里我们基本可以实现一个Promise的原型了
1 | function Promise(handle){ |
测试一下
1 | let promise = new Promise((resolve,reject) => { |
可以看出原型实现了我们的想要的预期效果,但是也同样也存在一些问题:只有执行同步任务时promise才能正常工作。当执行异步代码时将失效。在这里举一个例子。
1 | let promise = new Promise((resolve,reject) => { |
我们知道Promise解决的主要是异步问题,下面将实现一个可以执行异步调用的Promise
Promise的异步实现
异步调用时,当我们执行promise的then函数时,promise可能仍处于pending状态。这时对应的回调函数不能被执行。为了解决这个问题我们给Promise对象添加了两个回调函数数组,当promise实例处于pending状态时将回调函数加入回调函数数组。当状态发生改变时再进行执行
话不多说,上代码
1 | function Promise(handle){ |
做个测试
1 |
|
链式调用的实现
这样下来,我们就实现了异步的Promsie。但是根据Promise规范可知,promise的then函数会返回一个promise对象,这样才能够实现promise的链式调用。用过jQuery的人都知道,jQuery依靠return this
实现了链式调用,那么promise也是依靠返回this实现链式调用吗。
答案显然是否认的,promise then 函数的每一次调用,都会返回一个全新的promise实例。下面将实现promise的链式调用
1 | Promise.prototype.then = function(onResolve,onReject){ |
链式调用的错误处理
在Promise/A+ 规范中,对错误处理有这样一段描述
对于 promise2 = promise1.then(onReslove,onReject)
中,如果 onFulfilled
或者 onRejected
抛出一个异常 e
,则 promise2
必须拒绝执行,并返回拒因 e
。我们在代码中加上对错误的捕获。
1 |
|
这样就完成了对 Promise 链式操作的事件捕获
Promise的传递
最后我们需要实现Promise实现中,最难实现的部分。关于这部分的规范可以在Promise/A+规范
Promise 解决过程中看到。大致部分如下
如果 onFulfilled
或者 onRejected
返回一个值 x
,则运行下面的 Promise 解决过程:[[Resolve]](promise2, x)
如果
promise
和x
指向同一对象,以TypeError
为据因拒绝执行promise
如果
x
为 Promise ,则使promise
接受x
的状态- 如果
x
为对象或者函数:则将x视为promise对待,若在执行过程中出现错误e,则reject该错误。
1 | handleError(() => { |
下面实现promiserReslove 处理promise的解决问题。
1 | /** |
老规矩 测试一下
1 | const promise = new Promise((reslove,reject)=>{ |
值的穿透
以上,我们的promise好像已经差不多了,但是还有一个问题,需要处理。源码可以在hen中实现什么都不传。promise中管这种现象叫,值的穿透
。 因此,我们需要在then方法里,对then方法的入参进行容错处理:
1 | Promise.prototype.then = function(onReslove,onReject){ |
测试一次
1 | const promise = new Promise((reslove,reject)=>{ |
另外,promise规范中要求,所有的onFufilled
和onRejected
都需要异步执行,如果不加异步可能造成测试的不稳定性,所以我们给执行这两个方法执行的地方都加上异步方法。个人理解异步执行其实已经被callbacks回调解决。但是为了确保测试的稳定性,在这里给onFufilled
和onRejected
加上定时器
1 | const reslove = (value) => { |
以上,promise/A+ 规范 的实现就全部完成。
完整代码
1 | //Promise A+ |