blog
blog copied to clipboard
async/await 两面性
async/await 两面性
在工作中处理异步用到最多的还是Promise,没有经常用async/await是因为业务复杂度还不够。
不过对于这个 ES7 带来的新式的异步语法,还是值得说道说道。
the good part
简洁!async/await 给我们带来的最重要的好处是同步编程风格,也只是风格而已,它是Promise的语法糖。
任何async函数都会隐式返回一个promise,而promise的解析值将是return的值
意味着:
- Returning a non-Promise value fulfills p with that value.
- 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