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');

可以看到这段代码中用到了setTimeoutprocess.nextTicksetImmediate,这三个方法在JS中都是异步去执行的,输出结果如下

script start
script end
nextTick
setTimeout
setImmediate

为什么同样是异步方法process.nextTick中的回调函数就会在setTimeoutsetImmediate中的回调函数之前执行?这就和task和microtask的机制有关了。关于task和microtask这里有一篇文章(Tasks, microtasks, queues and schedules)讲述的非常好,可以观摩一下,学习学习。

为了更好的理解JS是如何运行的,可以看下图。


Promise的实现与标准_第1张图片
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方式运行的有setTimeOutsetImmediate,而已MicroTask方式运行的有process.nextTickMutationObserver。这也就是上面的例子中process.nextTick中回调优先执行的原因。因为process.nextTick中回调被添加到了Microtask Queue,而setTimeOutsetImmediate中的回调则被添加到了Task Queue的末尾,他们在之后的几轮loop中才会被执行。

这里需要提一下有些地方将Task称为MacroTask,将MicroTask称为Jobs。

而在一些具体的实现中,可能会存在多个Task Queue,根据不同的实现目的不同的Task Queue之间存在不同的优先级(例如有些浏览器可能更加注重UI渲染的性能,所以将UI相关任务的Task Queue优先级提高)。

猜测Promise的实现

熟悉Promise的人都知道Promise有三个状态,pendingreslovedrejected。一旦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状态时可以转换到resolvedrejected状态
  • 当Promise处于resolved状态时无法再转换到其他状态,并且有一个无法改变value
  • 当Promise处于rejected状态时无法再转换到其他状态,并且有一个无法改变的reason(reason一般为一个Error对象)

Promise的then方法

Promise的then方法接受两个参数

promise.then(onResolved, onRejected);
  • onResolvedonRejected参数都是可选的,如果onResolvedonRejected不是function,则忽略相应的参数。onResolvedonRejected都不能被调用超过一次。

  • onResolvedonRejected需要通过异步的方式执行,可以用“macro-task”或“micro-task”机制来执行。

  • 同一个Promise的then方法可以被调用多次,当该Promise状态变为resolvedrejected状态时,注册在该Promise上的回调应该根据注册的顺序被调用。

  • then方法会返回一个Promise

    promise2 = promise1.then(onResolved, onRejected);
    
    1. 如果onResolvedonRejected返回一个x,那么promise2的状态需要根据x来决定(至于如何决定promise2的状态,会在第三部分中说明)。
    2. 如果onResolvedonRejected抛出一个异常e,那么promise2必须rejected且reason = e
    3. 如果promise1是resolved状态且onResolved不是一个function那么promise2必须resolved,并且promise2的value必须与promise1相同
    4. 如果promise1是rejected状态且onRejected不是一个function那么promise2必须rejected,并且promise2的reason必须与promise1相同

The Promise Resolution Procedure

个人感觉这个标题不好“生翻”,直面的翻译可能反倒容易让人误解。可以把这个部分理解为一种操作,该操作需要接受两个参数(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.如果xpromise是同一个对象的引用(x === promise),那么reject promise并将一个TypeError赋值给reason

  • 2.如果x是一个Promise(x instanceof Promise),那么promise的状态入下:

    • 2.1 如果x处于pending状态那么promise也处于pending状态,直到x状态变为resolved或rejected。

    • 2.2 如果x处于resolved状态,那么用x的value来resolve promise

    • 2.3 如果x处于rejected状态,那么用x的reason来reject promise

  • 3.如果x是一个对象或function

    • 3.1 如果获取属性x.then的过程中抛出异常e,那么将e作为reason来reject promise

    • 3.2 如果x.then是一个function,那么调用x.then传入参数resolvePromiserejectPromise

      • 3.2.1 如果resolvePromise被调用且传入的参数为y,那么再次执行此操作,参数为(promise, y)

      • 3.2.2 如果rejectPromise被调用且传入的参数r,那么将r作为reason来reject promise

      • 3.2.3 如果resolvePromiserejectPromise同时被调用,或者被调用多次,那么优先处理第一次调用,之后的调用都应该被忽略。

      • 3.2.4 如果调用x.then抛出了异常e,若在抛出异常前resolvePromiserejectPromise已经被调用,那么忽略异常即可。若resolvePromiserejectPromise没有被调用过,那么将e作为reason来reject promise

    • 3.3 如果x.then不是一个function,那么用x来resolve promise

  • 4.如果x既不是对象也不是function,那么用x来resolve promise

你可能感兴趣的:(Promise的实现与标准)