# Promise

# Promise 用法

new Promise((resolve, reject) => {
  // do something
  resolve();
});
1
2
3
4

resolve()将 Promise 对象的状态由pending转变为fulfilled, 相应地, reject()执行时, Promise 对象的状态由pending转换为rejected.

# Promise#then()

new Promise()后返回一个 Promise 对象, 这个对象的then属性是一个函数, 也是我们最常用的属性. 它接受两个参数分别是fulfilledrejected的回调函数.

new Promise(...).then(
  function fulfilled() {},
  function rejected() {}
);
1
2
3
4

# Promise#catch()

catch用于处理 Promise 对象里抛出的错误, 实际上也是一个语法糖:

new Promise(...)
  .then(undefined, (e)=>{...})

// 等价于

new Promise(...)
  .catch((e)=>{...})
1
2
3
4
5
6
7

# Promise.resolve()/reject()

我们有时可以直接将 Promise 对象的状态直接改变, 无论是 fulfilled 还是 rejected. 可以分别使用Promise.resolve(value)Promise.rejected(value). 它也返回一个 Promise 对象, 这意味着我们可以继续使用thencatch.

Promise.resolve(1).then(value => {
  console.log(value);
}); // 1
1
2
3

# Promise.all()

当我们需要同时进行多个异步操作, 并且需要将这些异步结果拿到才能进行下一步工作时. 可以使用Promise.all()

const p1 = Promise.resolve(1);
const p2 = 32;
const p3 = axios("https://www.baidu.com");

Promise.all([p1, p2, p3]).then(res => {
  // handle result
});
1
2
3
4
5
6
7

当所有 promise 对象的状态都变为fulfilled后, promise 对象的resolve()的参数会传递到then()的回调函数里, 以数组的形式. 如果有一个状态变为rejected, 那么将把第一个错误的 promise 对象的rejected()参数传递给then()的回调函数里

# Promise.race()

race顾名思义, 就是比赛的意思. Promise.race()也接受一个可迭代的对象.

const p1 = Promise.resolve(1);
const p2 = 32;
const p3 = axios("https://www.baidu.com");

Promise.race([p1, p2, p3]).then(res => {
  // handle result
});
1
2
3
4
5
6
7

传入数组的 promise 对象将会竞争谁最先变为fulfilled或者rejected, 实际上也就是争谁状态变化最快, 最先的那个对象就能夺取下一个then()的使用权(听起来有点扯).

# Promise 永远异步地处理结果

考虑如下代码:

Promise.resolve(42).then(value => {
  console.log(value);
});
console.log("outer promise");

//outer promise
// 42
1
2
3
4
5
6
7

由于outer promise42之前先打印, 我们可以发现这个代码并不是同步地执行下去的. 那是由于在设计 JavaScript Promise 之初的考量: 同步调用和异步调用同时存在会导致混乱. 在之前的博文提到, 回调并不是只用于异步场景, 同步场景下一样可以使用回调. 那么, 早先的通过回调来解决异步就会带来执行顺序的问题:

function onReady(fn) {
  var readyState = document.readyState;
  if (readyState === "interactive" || readyState === "complete") {
    fn();
  } else {
    window.addEventListener("DOMContentLoaded", fn);
  }
}
onReady(function() {
  console.log("DOM fully loaded and parsed");
});
console.log("==Starting==");
1
2
3
4
5
6
7
8
9
10
11
12

上面代码有两个结果, 取决于 DOM 元素是否加载好.如果在调用onReady之前 DOM 已经载入的话,对回调函数进行同步调用.如果在调用onReady之前 DOM 还没有载入的话,通过注册 DOMContentLoaded 事件监听器来对回调函数进行异步调用. 在这里只是打印顺序发生了变化, 但是这个 bug 可能会引起一些难以解决的问题. 为了避免这样的问题, Promise 规定无论异步同步, 回调函数永远将会异步执行.

从技术的实现手段上, Promise被定义为微任务, 微任务队列里的 task 会在执行上下文的同步任务结束后开始执行.

下面是改写的版本:

function onReadyPromise() {
  return new Promise(function(resolve, reject) {
    var readyState = document.readyState;
    if (readyState === "interactive" || readyState === "complete") {
      resolve();
    } else {
      window.addEventListener("DOMContentLoaded", resolve);
    }
  });
}
onReadyPromise().then(function() {
  console.log("DOM fully loaded and parsed");
});
console.log("==Starting==");
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 错误处理

当我们的 Promise 链式调用出现错误时, 它是怎么处理的呢. 下面几张图会展示它的处理逻辑: resolve resolve

前一个 Promise 对象变为fulfilled, 那么就触发成功回调, 由于then()也返回一个新的 Promise 对象, 所以当成功回调成功执行后, 就会继续执行下一个成功回调. 一旦有 Promise 对象变为rejected, 那么就会触发失败回调, 当失败回调处理好后, 变化触发下一个成功回调; 如果失败回调也rejected了, 那么就会执行下一个的失败回调.

# 回调结果的传递

回调结果需要在回调函数里 return 给下一个回调函数:

Promise.resolve(12)
  .then(value => value + 1)
  .then(value => value * 2)
  .then(value => console.log(value));
// 26
1
2
3
4
5