1

什么是Promise

Promise代理了一个可能要在未来才能到达的值[[PromiseValue]]。Promise的一个最重要的特点是,你可以通过then来指定当[[PromiseValue]]到来时(或到来失败时)调用的handler

Promise的4种状态

  1. fulfilled - 成功,[[PromiseValue]]是成功获取到的值
  2. rejected - 失败,[[PromiseValue]]是失败的原因
  3. pending - [[PromiseValue]]还没有到达
  4. settled - [[PromiseValue]]已经有结果(fulfilled或rejected)

创建Promise

方式1:new Promise(executor)

new Promise( /* executor */ function(resolve, reject) { ... } );

传入Promise()的参数叫做executor,它封装了获取[[PromiseValue]]的过程

  1. 在初始化Promise的过程中,executor被Promise内部代码执行,并给executor传入2个参数:resolve函数, reject函数。

    MDN文档:The executor function is executed immediately by the Promise implementation, passing resolve and reject functions (the executor is called before the Promise constructor even returns the created object).
  2. 即使在executor中调用了resolve或reject,也会先执行完当前的executor函数体(不能像return一样直接退出函数体)

    var p1 = new Promise(function (res, rej) {
      console.log('before res');
      res("ok!");
      console.log('after res');
    });
    console.log('after p1 init, p1:', p1);
    
    // before res
    // after res  它被输出说明:即使在executor中调用了resolve或reject,也会先执行完当前的executor函数体,而不像return那样立即退出函数
    // after p1 init, p1: Promise { 'ok!' } 它在'before/after res'以后才被输出说明:Promise在初始化过程中就会同步地调用executor(called synchronously)
  3. executor的resolve或reject执行以后,Promise的状态就立刻改变(change synchronously)

    var p1 = new Promise(function (res, rej) {
      setImmediate(function() {
        res('haha');
        console.log('after res', p1); // executor的res函数执行完毕以后,p1状态已经变为fulfilled
      });
    });
    console.log('after init', p1);  // 由于executor的res或rej函数还未执行,p1处于pending状态
    
    // after init Promise { <pending> }
    // after res Promise { 'haha' }

方式2:Promise.resolve(value)

  • Promise.resolve(value);

返回一个fulfilled的Promise,[[PromiseValue]]为value。

  • Promise.resolve(promise);

直接返回参数promise。

  • Promise.resolve(thenable);

将thenable转换为Promise,Promise的状态和[[PromiseValue]]跟随thenable。thenable会在后文讨论。

方式3:Promise.reject(reason)

返回一个rejected的Promise。
Promise.reject(reason)就是下面代码的语法糖形式:

new Promise(function(resolve, reject){
    reject(reason);
});

它没有Promise.resolve(something)这么复杂,不管传入什么,它直接将参数reason作为reject原因,返回一个rejected Promise,即使你闲着没事干传一个Promise进去:

var p = Promise.resolve("res");
Promise.reject(p)
    .then((val) => {
        console.log('111', val);
    }, (err) => {
        console.log('222', err === p);
    });
// 222 true
为了方便debug,最好传入Error实例。

Promise的核心方法:then(onFulfilled[, onRejected])

先说说then模式

then模式:你先把成功和失败时要调用的handler传给then函数等到时机成熟以后(进入settled状态以后)then函数就帮你调用合适的那个handler。存在一个这样的then函数的对象叫做Thenable对象

// 一个简单的Thenable对象
var thenable = {
  then: function (onFulfilled, onRejected) {
    // setTimeout模拟一个需要花2秒的异步过程
    setTimeout(function () {
      var num = Math.random();
      if (num > 0.5) {
        onFulfilled(num);
      } else {
        onRejected(num);
      }
    }, 2000);
  }
}
// 使用方式
thenable.then(
  function (result) {
    console.log('get result:', result);
  },
  function (err) {
    console.log('get error:', err);
  });
then模式类似于我们经常使用的Callback模式。

说回Promise的then方法

Promise的then方法其实就是在普通的then模式的基础上增加了链式调用的功能:then函数返回Promise对象,前一个Promise对象进入settled状态以后才调用下一个then函数的handler。

Promise.prototype.then()涉及2个Promise对象:

  • 调用then方法的Promise对象,这里用p1表示
  • 调用then以后返回的Promise对象,这里用p2表示
p2 = p1.then(onFulfilled, onRejected);

then的作用就是,立即返回一个pending状态的Promise:p2,并在p1进入settled状态以后自动帮你调用handler

  • 如果进入fulfilled状态(成功),自动调用onFulfilled
  • 如果进入rejected状态(失败),自动调用onRejected

在调用完handler以后,会根据handler的返回值触发p2的状态改变

  • 如果handler返回一个普通值val,p2状态立即(synchronously)变化:pending-->fulfilled,且p2的[[PromiseValue]]为val。
  • 如果handler中throw一个错误err,p2状态立即(synchronously)变化:pending-->rejected,且p2的[[PromiseValue]]为err。
  • 如果handler返回一个settled的Promise对象temp,p2状态立即(synchronously)变化:pending-->与temp相同的状态,且p2的[[PromiseValue]]与temp的[[PromiseValue]]相同。
  • 如果handler返回一个pending的Promise对象temp,p2的状态不立即改变,而是等到temp进入settled状态以后,p2的状态再(异步地)改变:pending-->与temp相同的状态,且p2的[[PromiseValue]]与temp的[[PromiseValue]]相同。
在这里我们只用关注p2是如何改变的,在后文我会解释p2是什么时候改变的(同步还是异步)以及p2的handler是什么时候调用的。

举个例子:

Promise.resolve('result').then(onFulfilled1, onRejected1).then(onFulfilled2, onRejected2);

这等价于:

var p1 = Promise.resolve('result');
var p2 = p1.then(onFulfilled1, onRejected1);
var p3 = p2.then(onFulfilled2, onRejected2);
  • 因为p1是fulfilled状态,所以p1的成功handler——onFulfilled1被调用。
  • 如果onFulfilled1返回普通值(不是Promise),那么p2的状态变化:pending-->fulfilled,且p2的[[PromiseValue]]为onFulfilled1的返回值。接下来p2的成功handler——onFulfilled2被调用,且传入onFulfilled2的参数为p2的[[PromiseValue]]
  • 如果onFulfilled1返回的是Promise,那么p2的状态和[[PromiseValue]]都跟随这个被返回的Promise。onFulfilled2将在p2 fulfilled以后被调用,onRejected2将在p2 rejected以后被调用,传入的参数都是p2的[[PromiseValue]]
  • 依此类推,p3的状态和[[PromiseValue]]都取决于onFulfilled2/onRejected2的返回值。

handler何时被调用

  1. handler的调用异步于then的调用。

    A异步于B的意思:A与B在JavaScript消息队列中属于不同的消息。当前消息的调用栈完全退出以后,Event loop再处理下一个消息。Event loop处理完B消息以后可能要再处理0个或多个消息才能处理到A。
  2. 如果p1是通过new Promise(executor)的方式得到,那么除了满足第一条以外,p1 handler的调用还异步于executor中resolve()reject()的调用。
  3. 如果Promise是由then返回的:

    var p1 = Promise.resolve('haha');
    var p2 = p1.then(p1_handler);
    p2.then(p2_handler)

    那么除了满足第一条以外,如我在之前讨论then的时候所说:

    • 如果p1_handler产生同步的结果(包括返回普通值、抛出异常、返回settled Promise),则p2状态立即变化,且p2_handler立即调用。这两者都是同步于p1_handler进行的。
    • 如果p1_handler产生异步的结果(返回pending Promise,temp),则p2状态随着temp自动改变,且p2_handler在p2状态改变以后自动调用。这两者不仅异步于p1_handler进行,而且异步于temp的状态改变。

接下来我们一个一个地讨论。

1. handler的调用异步于then的调用

在调用then为p1指定handler以后,并不会立即触发handler的调用,而是向JavaScript消息队列中增加一个消息,然后继续执行then之后的代码。等到then所在的执行栈完全弹出,Event loop再处理下一个消息。处理完若干个消息以后,Event loop处理到handler的消息。处理这个消息的时候,先检查p1是否为settled,如果是,则调用对应的handler。

即使p1在调用then时就是settled状态,handler的调用也是异步的。p1在调用then时是pending状态的话就更不用说了。

var p1 = Promise.resolve('haha');
// p1在调用then时就是settled状态
p1.then((val) => {
  // 在nextTick以后,p1 handler才调用
  console.log('in p1 handler, p1:', p1);
});
console.log('after then called, p1:', p1);
process.nextTick(() => {
  console.log('nextTick, p1:', p1);
});

// after then called, p1: Promise { 'haha' }
// nextTick, p1: Promise { 'haha' }
// in p1 handler, p1: Promise { 'haha' }

这也是为什么then返回的p2(在刚被返回的时候)必定处于pending状态。因为p1 handler的调用异步于p1的产生(也就异步于p2的产生)。p2需要等待p1 handler异步调用并返回结果才能改变状态,因此在handler被调用以前,p2都是pending状态:

var p1 = Promise.resolve('haha');
console.log('p1', p1);
var p2 = p1.then((val) => {
  console.log('in p1 handler');
  return 'xixi';
});
console.log('p2', p2);
setImmediate(() => {
  console.log('setImmediate p2', p2);
});

// p1 Promise { 'haha' }
// p2 Promise { <pending> }
// in p1 handler
// setImmediate p2 Promise { 'xixi' }

2. handler的调用异步于executor中resolve()reject()的调用

如果p1是通过new Promise(executor)的方式得到,那么除了满足第一条以外,p1 handler的调用还异步于executor中resolve()reject()的调用。
也就是说resolve()reject()的调用并不会立即触发handler的调用,而是向JavaScript消息队列中增加一个消息,等待Event loop处理到这个消息。处理这个消息的时候会调用handler。
例子(用node.js运行):

var global_val = 'old value';

var p1 = new Promise((res) => {
  setTimeout(function () {
    res('haha');
    console.log(p1); // p1的状态立刻改变
    console.log('immediately', global_val); // 但是此时p1的handler还没有调用
    process.nextTick(function () {
      console.log('nextTick', global_val);  // 此时p1的handler还是没有调用
    });
    setImmediate(function() {
      console.log('setImmediate', global_val);  // 此时p1的handler已经调用
    });
  }, 1000);
});
var p2 = p1.then(() => {
  // p1的handler
  console.log('in p1 handler');
  global_val = 'new value';
});

// Promise { 'haha' }
// immediately old value
// nextTick old value
// in p1 handler
// setImmediate new value
为什么handler要异步于executor的resolve()reject()调用

因为在executor的resolve()reject()的调用以后可能还有其他代码要同步执行(当前handler还没有结束)。前一个handler都还没有执行完,自然不应该开始下一个handler的执行。(handler的执行不应该嵌套,而应该串行)

比如在上面global_val的例子中,传入setTimeout的函数就是一个handler,调用res('haha')的时候这个handler还有很多代码要执行。那么下一个handler(p1的handler)不应该打断这些代码的执行。

3. 如果Promise是由then返回的

如果Promise是由then返回的:

var p1 = Promise.resolve('haha');
var p2 = p1.then(p1_handler);
p2.then(p2_handler)

那么除了满足第一条以外,如我在之前讨论then的时候所说:

  • 如果p1_handler产生同步的结果(包括返回普通值、抛出异常、返回settled Promise),则p2状态立即变化,且p2_handler立即调用。这两者都是同步于p1_handler进行的。
  • 如果p1_handler产生异步的结果(返回pending Promise,temp),则p2状态随着temp自动改变,且p2_handler在p2状态改变以后自动调用。这两者不仅异步于p1_handler进行,而且异步于temp的状态改变。

这里给出一个测试代码供大家自行验证,注释中有说明,并且可以通过注释/解注释来修改p1 handler的返回结果。

// 如果p1_handler产生同步的结果(包括返回普通值、抛出异常、返回settled Promise),则p2状态立即变化,且p2_handler立即调用。这两者都是同步于p1_handler进行的。
// 如果p1_handler产生异步的结果(返回pending Promise,temp),则p2状态随着temp自动改变,且p2_handler在p2状态改变以后自动调用。这两者不仅异步于p1_handler进行,而且异步于temp的状态改变。

var p1 = Promise.resolve('haha');

var p2 = p1.then((val) => {
    console.log('in p1 handler. p1:', p1, 'p2', p2, 'p3:', p3);
    process.nextTick(function () {
        // 当p1 handler产生同步的结果时,p2 handler在nextTick之前就被调用,且p2在nextTick时已经settled。说明p2状态改变、p2 handler的调用是同步于p1 handler的。
        // 当p1 handler产生异步的结果时,p2 handler在nextTick之后才被调用,且p2在nextTick时依然pending。说明p2状态改变、p2 handler的调用是异步于p1 handler的。
        console.log('p1 handler nextTick. p1:', p1, 'p2', p2, 'p3:', p3);
    });

    // throw 'err!';
    // return Promise.resolve('heihei');
    // return Promise.reject('heihei');
    // return new Promise(res => {     // temp Promise
    //     setTimeout(function () {
    //         res('heihei');
    //         process.nextTick(function () {
    //             // p2在nextTick时依然是pending,说明p2的状态改变异步于temp的状态改变
    //             console.log('resolve nextTick. p1:', p1, 'p2', p2, 'p3:', p3);
    //         });
    //     }, 1000);
    // });
    return 'heihei';
});

var p3 = p2.then(val => {
    console.log('in p2 success handler. p1:', p1, 'p2', p2, 'p3:', p3);
    return 'xixi';
}, err => {
    console.log('in p2 error handler. p1:', p1, 'p2', p2, 'p3:', p3);
    return 'hoho';
});

process.nextTick(function () {
    // 它先于'in p1 handler'输出可以说明handler是异步于then调用的
    console.log('after then called. p1:', p1, 'p2', p2, 'p3:', p3);
});

异常处理

Promise rejected 后,将跳至带有拒绝回调的下一个 then()(或具有相同功能的 catch())。如果有then(func1, func2),则 func1 或 func2 中的一个将被调用,而不可能二者均被调用。但如果是 then(func1).catch(func2),则有可能两者均被调用(func1 rejected时)。

asyncThing1().then(function() {
  return asyncThing2();
}).then(function() {
  return asyncThing3();
}).catch(function(err) {
  return asyncRecovery1();
}).then(function() {
  return asyncThing4();
}, function(err) {
  return asyncRecovery2();
}).catch(function(err) {
  console.log("Don't worry about it");
}).then(function() {
  console.log("All done!");
})

异常处理路径

蓝线表示 fulfilled 的 promise 路径,红路表示 rejected 的 promise 路径。

如果在promise构造函数的回调函数中then的回调函数中发生了 JavaScript 异常(throw Errow),返回的promise会自动reject。

var jsonPromise = new Promise(function(resolve, reject) {
  // JSON.parse throws an error if you feed it some
  // invalid JSON, so this implicitly rejects:
  resolve(JSON.parse("This ain't JSON"));
});

jsonPromise.then(function(data) {
  // This never happens:
  console.log("It worked!", data);
}).catch(function(err) {
  // Instead, this happens:
  console.log("It failed!", err);
})

最佳实践:缓存Promise——重复利用异步操作的结果

var storyPromise;

function getChapter(i) {
  storyPromise = storyPromise || getJSON('story.json');

  return storyPromise.then(function(story) {
    return getJSON(story.chapterUrls[i]);
  })
}

// and using it is simple:
getChapter(0).then(function(chapter) {
  console.log(chapter);
  return getChapter(1);
}).then(function(chapter) {
  console.log(chapter);
})

假设我们要分别获取story的各个章节(用getChapter函数),每个章节所在的URL存储在story.jsonchapterUrls数组中。
我们不需要每次要获取章节的时候都先获取一次story.json在拿到章节所在URL,更好的做法是:调用一次getJSON('story.json')就将这个Promise缓存到storyPromise中,将来需要请求story.json的时候只需要重复利用这个fulfilledstoryPromise。避免了发送重复的HTTP请求。

Promise一旦settled,[[PromiseValue]]不会再改变,我们可以将它看作一个定值,多次使用。
因为Promise对象无法被外部改变(无论是意外地还是恶意地),我们可以安全地将这个对象交给其他库使用,而不用担心库会修改到Promise的结果。

最佳实践:一个 Promise fulfilled 以后再执行下一个 Promise

除了手动写then回调来依次执行Promise以外,对于一个数组的任务,我们可以利用array.reduce的循环来创建then回调

// Loop through our chapter urls
story.chapterUrls.reduce(function(sequence, chapterUrl) {
  // 上一个Promise fulfilled以后才执行下一个getJSON
  // 从而保证每一个章节是顺序请求的,从而在页面中是顺序显示的
  return sequence.then(function() {
    return getJSON(chapterUrl);
  }).then(function(chapter) {
    addHtmlToPage(chapter.html);
  });
}, Promise.resolve())

参考资料

https://developers.google.com...

https://developer.mozilla.org...

http://liubin.org/promises-book/

Node 定时器详解讲述了process.nextTick()和Promise回调函数(microtask)的执行顺序。


csRyan
1.1k 声望198 粉丝

So you're passionate? How passionate? What actions does your passion lead you to do? If the heart doesn't find a perfect rhyme with the head, then your passion means nothing.