Promise的实现与标准
转载同事写的文章,从promise的标准角度来说明其实现,这样不管在看Q,还是bluebird的时候,都会容易很多。
JS是如何运行的
每当谈起JS的时候,单线程,异步,回调,非阻塞,event loop这些词汇总是会出现。但是JS到底是如何运行的呢,不妨看一看Philip Roberts在JSConf上讲解event loop的视频。Philip Roberts还自己动手做了一个JS runtime的可视化程序,这个可视化程序他在演讲中也有展示。
让我们来看一段代码吧
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
process.nextTick(function() {
console.log('nextTick');
});
setImmediate(function() {
console.log('setImmediate');
});
console.log('script end');
可以看到这段代码中用到了
setTimeout
、process.nextTick
和setImmediate
,这三个方法在JS中都是异步去执行的,输出结果如下script start
script end
nextTick
setTimeout
setImmediate
为什么同样是异步方法
process.nextTick
中的回调函数就会在setTimeout
和setImmediate
中的回调函数之前执行?这就和task和microtask的机制有关了。关于task和microtask这里有一篇文章(Tasks, microtasks, queues and schedules)讲述的非常好,可以观摩一下,学习学习。为了更好的理解JS是如何运行的,可以看下图。
文章图片
image.png 在JS运行的过程中,event loop每一次循环都会将一个task从
Task Queue
中取出,task执行的过程中会调用不同的函数并压栈。栈中的代码会调用一些API,当这些API执行结束后会将完成的任务加入Task Queue
(具体的实现因API而异,有些API可能会在单独的线程中去处理这些操作)。当stack
中的代码全部执行完成时会再次从Task Queue
中取出一个新的任务来执行,这样就开始了新的一轮loop。那么Microtask是在什么时候执行的呢?JS会在每一轮loop结束,也就是
stack
中的代码全部执行完毕时,去执行Microtask Queue
中的任务。当Microtask Queue
中的任务全部执行完成后再从Task Queue
中取出下一个任务。可以理解为执行过程为Task1 -> Microtask ->Task2以Task方式运行的有
setTimeOut
、setImmediate
,而已MicroTask方式运行的有process.nextTick
、MutationObserver
。这也就是上面的例子中process.nextTick
中回调优先执行的原因。因为process.nextTick
中回调被添加到了Microtask Queue
,而setTimeOut
和setImmediate
中的回调则被添加到了Task Queue
的末尾,他们在之后的几轮loop中才会被执行。这里需要提一下有些地方将Task称为MacroTask,将MicroTask称为Jobs。而在一些具体的实现中,可能会存在多个
Task Queue
,根据不同的实现目的不同的Task Queue
之间存在不同的优先级(例如有些浏览器可能更加注重UI渲染的性能,所以将UI相关任务的Task Queue优先级提高)。猜测Promise的实现 熟悉Promise的人都知道Promise有三个状态,
pending
、resloved
和rejected
。一旦Promise的状态发生改变就再也不会变动,且Promise包含的值也不会被改变。//e.g.1
console.log('script start');
let promise = new Promise(function(resolve, reject) {
console.log('in promise');
resolve('reslove promise');
});
promise.then(function(value) {
console.log('resolve: ', value);
}, function(reason) {
console.log('reason: ', reason);
});
console.log('script end')
上面这段代码对于经常使用Promise的人再简单不过了,可以看下他的输出结果。
script start
in promise
script end
resolve:reslove promise
从
e.g.1
的输出结果可以看到传给then方法的回调是在最后执行的,所以可以判断出new Promise(function)
中的function是同步执行的,而then(reslove,reject)
中的resolve或reject是异步执行的。熟悉Promise的人对下面一段代码也自然不会感到陌生。
//e.g.2
promise.then((value) => {
//do some stuff
}).then((value) => {
//do some stuff
}).then((value) => {
//do some stuff
}).catch((reason) => {
//do some stuff
});
为什么Promise能写成链式的,在
.then
之后还能接着.then
?基于这一点可以判断出then方法return的是一个Promise,那么既然是Promise就一定会有状态,那么调用then之后return的这个Promise的状态是如何确定的呢?接着看下面的栗子。//e.g.3
let promise1 = new Promise(function(resolve, reject) {
resolve('reslove promise');
});
let promise2 = promise1.then(function onReslove(value) {
console.log('1 resolve: ', value);
return 1;
}, function onReject(reason) {
console.log('1 reason: ', reason);
});
promise2.then(function onReslove(value) {
console.log('2 resolve: ', value);
}, function onReject(reason) {
console.log('2 reason: ', reason);
});
执行结果:
1 resolve:reslove promise
2 resolve:1
可以看到当在
onReslove
中返回一个基础类型的时候promise2
的状态变成了resolved
。如果把上面的
return 1;
改为throw new Error('error');
会是什么样呢?输出结果如下:1 resolve:reslove promise
2 reason:Error: error
at onReslove (/Users/lx/Documents/projects/VsTest/PromiseExample.js:40:11)
at
at process._tickCallback (internal/process/next_tick.js:188:7)
at Function.Module.runMain (module.js:607:11)
at startup (bootstrap_node.js:158:16)
at bootstrap_node.js:575:3
可以看到此时
promise2
的状态变为了rejected
。那么如果我在onResolve()
中return一个处于不同状态Promise会怎么样呢?//e.g.4
let promise1 = new Promise(function(resolve, reject) {
resolve('reslove promise');
});
let promise2 = promise1.then(function onReslove(value) {
console.log('1 resolve: ', value);
return promiseReturn;
}, function onReject(reason) {
console.log('1 reason: ', reason);
});
promise2.then(function onReslove(value) {
console.log('2 resolve: ', value);
return 1;
}, function onReject(reason) {
console.log('2 reason: ', reason);
});
//依次使promiseReturn等于以下值://pending状态的Promise,5s后变为resolved状态
let promiseReturn = new Promise(function(reslove, reject) {
setTimeout(() => {
reslove(1)
}, 5000);
});
//输出结果为:
1 resolve:reslove promise
//5s之后
2 resolve:1//resolved状态的Promise
let promiseReturn = Promise.resolve(1);
//输出结果为:
1 resolve:reslove promise
2 resolve:1//rejected状态的Promise
let promiseReturn = Promise.reject(new Error('error'));
//输出结果为:
1 resolve:reslove promise
2 reason:Error: error
at Object. (/Users/lx/Documents/projects/VsTest/PromiseExample.js:33:36)
at Module._compile (module.js:569:30)
通过上面的例子可以看到,当
onResolve()
return一个Promise时,promise2的状态是和return的Promise的状态相同的。PromiseA+标准 [图片上传失败...(image-a86d48-1516949283239)]
ES标准中的Promise,Q以及bluebird都是PromiseA+标准的实现。 PromiseA+标准主要从三部分提出了对Promise实现的要求,第一部分规定了Promise的状态已经状态的变化。第二部分则指定Promise的
then
方法的行为。第三部分则是说明了如何决定then
方法返回的Promise的状态,并且支持了不同PromiseA+标准实现的Promise之间的兼容性。PromiseA+标准如下(更具体的标准戳这里):
Promise的状态
Promise必须处于
pending
,resolved
,rejected
三个状态之一- 当Promise处于
pending
状态时可以转换到resolved
或rejected
状态 - 当Promise处于
resolved
状态时无法再转换到其他状态,并且有一个无法改变value
- 当Promise处于
rejected
状态时无法再转换到其他状态,并且有一个无法改变的reason
(reason一般为一个Error对象)
Promise的then方法接受两个参数
promise.then(onResolved, onRejected);
onResolved
和onRejected
参数都是可选的,如果onResolved
或onRejected
不是function,则忽略相应的参数。onResolved
和onRejected
都不能被调用超过一次。
onResolved
和onRejected
需要通过异步的方式执行,可以用“macro-task”或“micro-task”机制来执行。
- 同一个Promise的
then
方法可以被调用多次,当该Promise状态变为resolved
或rejected
状态时,注册在该Promise上的回调应该根据注册的顺序被调用。
-
then
方法会返回一个Promise
promise2 = promise1.then(onResolved, onRejected);
- 如果
onResolved
或onRejected
返回一个x
,那么promise2
的状态需要根据x
来决定(至于如何决定promise2
的状态,会在第三部分中说明)。 - 如果
onResolved
或onRejected
抛出一个异常e
,那么promise2
必须rejected且reason = e
。 - 如果
promise1
是resolved状态且onResolved
不是一个function那么promise2
必须resolved,并且promise2
的value必须与promise1
相同 - 如果
promise1
是rejected状态且onRejected
不是一个function那么promise2
必须rejected,并且promise2
的reason必须与promise1
相同
- 如果
个人感觉这个标题不好“生翻”,直面的翻译可能反倒容易让人误解。可以把这个部分理解为一种操作,该操作需要接受两个参数
(promise, x)
,会根据x
的情况来决定promise
的状态。在我们的
onResolved
回调中一般会return一个value(如果没有写return xxx,那么value就等于undefined)。这里就可以把x
当做这个value。调用then
方法时返回的Promise的状态就是由这个x
来决定的。如果
x
是一个thenable(带有then方法的对象或function),那么可以假设x
和Promise的行为相似。这一点是为了让不同PromiseA+标准的实现可以兼容。The Promise Resolution Procedure这个操作的步骤如下:
- 1.如果
x
和promise
是同一个对象的引用(x === promise
),那么rejectpromise
并将一个TypeError
赋值给reason
- 2.如果
x
是一个Promise(x instanceof Promise
),那么promise
的状态入下:
- 2.1 如果
x
处于pending状态那么promise
也处于pending状态,直到x
状态变为resolved或rejected。
- 2.2 如果
x
处于resolved状态,那么用x
的value来resolvepromise
。
- 2.3 如果
x
处于rejected状态,那么用x
的reason来rejectpromise
- 2.1 如果
- 3.如果
x
是一个对象或function
- 3.1 如果获取属性
x.then
的过程中抛出异常e
,那么将e
作为reason来rejectpromise
- 【Promise的实现与标准】3.2 如果
x.then
是一个function,那么调用x.then
传入参数resolvePromise
和rejectPromise
- 3.2.1 如果
resolvePromise
被调用且传入的参数为y
,那么再次执行此操作,参数为(promise, y)
- 3.2.2 如果
rejectPromise
被调用且传入的参数r
,那么将r
作为reason来rejectpromise
- 3.2.3 如果
resolvePromise
和rejectPromise
同时被调用,或者被调用多次,那么优先处理第一次调用,之后的调用都应该被忽略。
- 3.2.4 如果调用
x.then
抛出了异常e
,若在抛出异常前resolvePromise
或rejectPromise
已经被调用,那么忽略异常即可。若resolvePromise
或rejectPromise
没有被调用过,那么将e
作为reason来rejectpromise
- 3.2.1 如果
- 3.3 如果
x.then
不是一个function,那么用x
来resolvepromise
- 3.1 如果获取属性
- 4.如果
x
既不是对象也不是function,那么用x
来resolvepromise
推荐阅读
- 热闹中的孤独
- JAVA(抽象类与接口的区别&重载与重写&内存泄漏)
- 放屁有这三个特征的,请注意啦!这说明你的身体毒素太多
- 一个人的旅行,三亚
- 布丽吉特,人生绝对的赢家
- 慢慢的美丽
- 尽力
- 一个小故事,我的思考。
- 家乡的那条小河
- 《真与假的困惑》???|《真与假的困惑》??? ——致良知是一种伟大的力量