Bb5f0eb29e59f5bd00fefa8d23eabf51
async/await 应用探索

tags: node,async,await,promise

[注:以下代码都在支持 Promise 的 Node 环境中实现]

1 promise 释义

promise 是抽象异步处理的对象,其提供了一系列处理异步操作的方法。

1.1 语法

const promiseA = new Promise((resolve, reject)=>{
    // 异步操作
    // 操作结束,使用 resolve()返回结果;使用 reject()处理错误
})
promiseA.then(onFulfilled, onRejected);

例子1-1:

const promiseA = new Promise(()=>{
  setTimeout(()=>{
    resolve('3秒后返回了A');
  }, 3000)
});
promiseA.then((res)=>{
  console.log(res);
});

1.2 static method

Promise 这样的全局对象还拥有一些静态方法。

包括 Promise.all() 还有 Promise.resolve() 等在内,主要都是一些对Promise进行操作的辅助方法。

1.2.1 Promise.all

Promise.all 接收一个promise对象数组作为参数,当这个数组里的所有promise对象全部变为resolvereject状态的时候,它才会去调用.then方法。
用于需要同时触发多个异步操作,并在所有异步操作都执行结束以后才调用.then
Promise.all 里有一个 promise 返回错误的时候就调用 catch() 了。测试代码如下:

const promiseA = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('promise A.');
  }, 1000);
});

const promiseB = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('error B');
    // resolve('promise B');
  }, 1500);
});

const promiseC = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('error c');
  }, 1000);
});

Promise.all([promiseA, promiseB, promiseC]).then((res)=>{
  console.log(res);
}).catch((err) => {
  console.log(err);
});
// 结果:error c

这点和预期的不同。具体描述可以看 MDN 的文档,这里摘录一部分:

The Promise.all() method returns a single Promise that resolves when all of the promises in the iterable argument have resolved or when the iterable argument contains no promises. It rejects with the reason of the first promise that rejects.

  • 思考:那么在并行执行所有 promise过程中,在存在 reject 的情况下如何获取其余 resolve 的全部结果?
    似乎并没有单独的 method来处理,需要封装一个方法。

1.2.2 Promise.resolve

静态方法Promise.resolve(value)可以认为是new Promise()方法的快捷方式。如:

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

但初始化Promise对象建议仍然使用new PromisePromise.resove的另一个作用是将thenable对象转换为promise对象。
ES6 Promise里提到了Thenable的概念,简单来讲它是非常类似于promise的东西。就好像有些具有.length方法的非数组对象被称为Array likethenable指的是具有.then方法的对象。
这种将thenable对象转换为promise对象的机制要求thenable对象所拥有的then方法应该和Promise所拥有的then 方法具有同样的功能和处理过程,在将thenable对象转换为promise``对象的时候,还会巧妙的利用thenable对象原来具有的then方法。最简单的例子就是jQuery.ajax(),它的返回值就是thenable。下面看看如何将thenable对象转换为promise对象。

const promiseA = Promise.resolve($.ajax('/json/comment.json')); // => promise 对象
promiseA.then((value)=>{
  console.log(value);
})

需要注意的是jQuery.ajax()返回的是一个具有.then方法的jqXHR Object对象,这个对象继承了来自Deferred Object的方法和属性。
但是Deferred Object并没有遵循PormisesA+ES6 Promises标准,所以即使看上去对象转换为了promise对象,其实还是缺失了部份信息。即使一个对象具有.then方法,也不一定就能作为ES6 Promises对象使用。
这种转换 thenable的功能除了在编写使用Promises的类库的时候需要了解之外,通常作为end-user不会使用到此功能。

1.2.3 Promise.race

Promise.racePromise.all类似,同样对多个promise对象进行处理,同样接收一个promise对象数组。Promise.race只要有一个promise对象进入Fullfilled或者Rejected状态的话,就会执行.then.catch方法。
测试代码如下:

const promiseA = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('promise A.');
  }, 1000);
});

const promiseB = new Promise((resolve, reject) => {
  setTimeout(() => {
    // reject('error B');
    resolve('promise B');
  }, 1500);
});

const promiseC = new Promise((resolve, reject) => {
  setTimeout(() => {
    // reject('error c');
    resolve('promise c');
  }, 500);
});

Promise.race([promiseA, promiseB, promiseC]).then((res)=>{
  console.log(res);
}).catch((err) => {
  console.log(err);
});

1.2.4 Promise.reject

通过调用Promise.reject()可以将错误对象传递给onRejected 函数。

Promise.reject(new Error("BOOM!"))
    .catch((error)){
      console.log(error);
    }

这个方法并不常用。

1.3 promise 状态

new Promise实例化的 promise 对象有三种状态:

  • 'has resolution' => 'Fulfilled'
    resolve(成功)时,会调用 onFulfilled。
  • 'has rejected' => 'Rejected'
    reject(失败)时,会调用 onRejected。
  • 'unresolved' => 'Pending'
    promise 对象刚被创建后的初始状态。

promise对象的状态,从 Pending 转换为 Fulfilled 或 Rejected 之后,promise 对象的状态就不再改变。因此,在 .then()内执行的函数只会调用一次。

异常处理:then or catch?

.catch 方法可以理解为 promise.then(undefined, onRejected)。但两者有不同之处:

  1. 使用promise.then(onFulfilled, onRejected) 的话,在 onFulfilled 中发生异常的话,在 onRejected 中是捕获不到这个异常的。
  2. 在 promise.then(onFulfilled).catch(onRejected) 的情况下,then 中产生的异常能在 .catch 中捕获。
  3. then 和 .catch 在本质上是没有区别的,但需要根据1,2点的差异选择适用的场合。
    测试对比代码如下:
const promiseA = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('1s test.');
  }, 1000);
});

promiseA.then((res)=>{
  throw new Error('handler err');
}).catch((err)=>{
  console.log(`promiseA ${err}`);
})
const promiseA = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('1s test.');
  }, 1000);
});

promiseA.then((res) => {
  throw new Error('handler err');
}, (err)=>{
  console.log(`promiseA ${err}`);
});

2 async/await 简介

Node7 通过 --harmony_async_await参数支持 async/await ,而 async/await 由于其可以用同步形式的代码书写异步操作,能彻底杜绝‘回调地狱’式代码。
async/await 基于 Promise, 是 Generator 函数的语法糖。async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。当函数执行时,一旦遇到await就先返回,等到触发的异步操作完成,再接着执行函数体后面的语句。示例代码如下:

function asynchornous(timer) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('测试 async/await');
    }, timer);
  });
}

async function test() {
  const time0 = new Date();
  const res = await asynchornous(2000);
  const time1 = new Date();
  console.log(`返回 => ${res},用时:${Math.floor((time1 - time0)/1000)}s`);
}

test();

2.1 await 的用法

await 命令必须用到 async 函数中,且其后应该是一个 Promise 对象。如果不是,会被转化为一个立即 resolvePromise 对象。
只要一个 await命令后面的 Promise 对象变为 reject 状态,那么整个 async 函数都会中断执行。

async function test() {
  await Promise.reject('error');
  await Promise.resolve('test'); // 不会执行
}

这时如果我们希望前一个异步操作失败后,不中断后面的异步操作,可以捕获前一个异步操作的错误。另一种写法是在 await后面的 Promise 对象后再跟上 catch方法。示例代码如下:

async function test() {
  await Promise.reject('error')
      .catch(err => console.log(err));
  const res = await Promise.resolve(`test`);
  console.log(res);
}
test();
// 执行结果:
// error
// test

2.2 捕获错误

await 命令后的 Promise对象,运行结果可能是 rejected,这样等同于 async函数返回的 Promise 状态为 rejected。 所以可以把 await 命令放到 try...catch 代码中。示例代码如下:
```js

top Created with Sketch.