blog icon indicating copy to clipboard operation
blog copied to clipboard

async/await 两面性

Open nanyang24 opened this issue 5 years ago • 0 comments

img

async/await 两面性

在工作中处理异步用到最多的还是Promise,没有经常用async/await是因为业务复杂度还不够。

不过对于这个 ES7 带来的新式的异步语法,还是值得说道说道。

the good part

简洁!async/await 给我们带来的最重要的好处是同步编程风格,也只是风格而已,它是Promise的语法糖。

任何async函数都会隐式返回一个promise,而promise的解析值将是return的值

意味着:

  1. Returning a non-Promise value fulfills p with that value.
  2. Returning a Promise means that p now mirrors the state of that Promise.
// async
async getBooksByAuthorWithAwait(authorId) {
  const books = await bookModel.fetchAll();
  return books.filter(b => b.authorId === authorId);
}

// promise
getBooksByAuthorWithPromise(authorId) {
  return bookModel.fetchAll()
    .then(books => books.filter(b => b.authorId === authorId));
}

async 改变了常规的异步调用写法

the bad part

异步等待

初次接触可能会带来误区,虽然await可以使代码看起来像同步,但事实上仍为异步。

await关键字,这将告诉JavaScript不立即执行下一行,而是等待promise返回解析值之后执行下一行 通常 await 命令后面是一个 Promise 对象(大多数情况是我们发起请求之后返回的promise)。如果不是,会被转成一个立即resolve的 Promise 对象。

async getBooksAndAuthor(authorId){ 
  const books = await bookModel.fetchAll(); 	// 1
  const author = await authorModel.fetch(authorId);	// 2 

  return { 
    author,
    books:books.filter(book => book.authorId === authorId),
  }; 
}

第二行代码 会直到 bookModel.fetchAll() 返回之后才会执行

但其实 authorModel.fetch(authorId) 不依赖于bookModel.fetchAll()的结果,所以实际上它们可以并行调用!但是,通过await在这里使用,这两个调用变为“同步”顺序,并且总执行时间将比并行版本长得多。

所以改进之后的版本:

async getBooksAndAuthor(authorId){ 
  const bookPromise = bookModel.fetchAll(); 
  const authorPromise = authorModel.fetch(authorId); 

  const book = await bookPromise; 
  const author = await authorPromise; 

  return { 
    author,
    books:books.filter(book => book.authorId === authorId),
  }; 
}

异步函数(bookModel.fetchAll() 和 authorModel.fetch(authorId))将返回一个promise,可以在以后使用它,并且bookPromise 和 authorPromise 将并行调用。 所以多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发

这里是利用到了 promise 的特性,你可以在一行中得到一个 promise 并等待它在另一行中resolve 或 reject

至于之后为什么要 await bookPromise 和 await authorPromise,是因为编译器不会知道我们要等待函数完成执行。因此,编译器将退出程序而不完成异步任务。所以我们需要 await 关键字。

当然这其实不属于 async 不好的部分,相反,如果你的需求是调用第二行代码是依赖于第一行代码的返回值,使用 async 可就比使用 promise 简单清晰的多(你懂得,promise会写出多层 .then 嵌套)。

错误处理

只要一个await语句后面的 Promise 变为reject,那么整个async函数都会中断执行。

async function f() {
  await Promise.reject('出错了');
  await Promise.resolve('hello world'); // 不会执行
}

上面代码中,第二个await语句是不会执行的,因为第一个await语句状态变成了reject。

有时,我们希望即使前一个异步操作失败,也不要中断后面的异步操作。 这时可以将第一个await放在try...catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行。

async function f() {
  try {
    await Promise.reject('出错了');
  } catch(e) {
  }
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// hello world

另一种方法是await后面的 Promise 对象再跟一个catch方法,处理前面可能出现的错误。

async function f() {
  await Promise.reject('出错了')
    .catch(e => console.log(e));
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// 出错了
// hello world

nanyang24 avatar Aug 08 '18 09:08 nanyang24