最近公事甚多,很久没学习了,本身也撸了个小网站,欢迎 star。javascript
JavaScript async/await: The Good Part, Pitfalls and How to Usejava
ES7 推出的 async/await
特性对 JS 的异步编程是一个重大的改进。在不阻塞主线程的状况下,它为咱们提供了使用同步代码风格去异步获取资源的能力。固然使用它也是须要一些技巧,这篇文章咱们从不一样角度去探索 async/await
,为你展现如何正确、高效的使用它们。编程
async/await
优势它最大的优势就是给咱们带来同步代码风格。见代码:json
// async/await
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/await
版本比 promise
版本更简单易懂。若是你忽略 await
关键字,那么代码就如同其余同步编程语言,如 Python
。promise
优势不只仅是可读性,async/await
已经被浏览器原生支持。现在,全部主流浏览器已经彻底支持。浏览器
原生支持,意味着你没必要转换代码,而更重要的是有利于调试。当你在函数的 await
代码行打上断点,而后步进到下一行时,你会发现调试器在 bookModel.fetchAll()
操做的时候进行了短暂的停留,而后才真正的步进到 .filter
代码行!这比 promise
调试更方便,由于你须要在 .fliter
代码行再打一个断点。安全
另外一个不多被人注意到的优势是 async
关键字。它代表了 getBooksByAuthorWithAwait()
函数的返回值必定是个 promise
,因此它的调用者可使用 getBooksByAuthorWithAwait().then(...)
或者安全的使用 await getBooksByAuthorWithAwait()
。见代码(错误的实践!):babel
getBooksByAuthorWithPromise(authorId) {
if (!authorId) {
return null;
}
return bookModel.fetchAll()
.then(books => books.filter(b => b.authorId === authorId));
}
}
复制代码
上面的代码段中,getBooksByAuthorWithPromise
可能会返回一个 promise
(正常状况)或者 null
值(异常状况),然后者这种状况,调用者没法安全的使用 .then()
。而有了 async
声明,就会避免这种不肯定性。异步
async/await
有时具备误导性一些文章会比较 async/await
和 promise
并声称它是下一代 JS
异步编程,而我不一样意这种观点。async/await
的确是一种改进,但它不过是个语法糖,不会完全改变咱们的编程风格。async
本质来讲,async
函数仍然是 promises
。在正确的使用 async
以前,你须要理解 promise
,可能你在使用 async
的过程当中也须要使用到 promise
。
回顾一下上面代码中的 getBooksByAuthorWithAwait()
和 getBooksByAuthorWithPromises()
函数,他们不只功能彻底相同,并且具备相同的接口。
这意味着,直接调用 getBooksByAuthorWithAwait()
会返回一个 promise
。
这不见得是件坏事,而多数人认为 await
可让异步函数变为同步函数的想法才是错误的。
async/await
陷阱哪么咱们在使用 async/await
会犯哪些错误呢?如下是一些常见点。
尽管 await
能让咱们的代码看起来同步化,但要牢记它们仍然是异步的内容,因此值得咱们去关注代码以免太同步化。
async getBooksAndAuthor(authorId) {
const books = await bookModel.fetchAll();
const author = await authorModel.fetch(authorId);
return {
author,
books: books.filter(book => book.authorId === authorId),
};
}
复制代码
这段代码看上去没有什么问题,可是它是错误的。
await bookModel.fetchAll()
会等待 fetchAll()
返回await authorModel.fetch(authorId)
才会被调用注意到 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),
};
}
复制代码
而若是你想依次获取一个列表中的全部项,你必须依赖 promises
:
async getAuthors(authorIds) {
// 错误,这会致使`串行执行`
// const authors = _.map(
// authorIds,
// id => await authorModel.fetch(id));
// 正确
const promises = _.map(authorIds, id => authorModel.fetch(id));
const authors = await Promise.all(promises);
}
复制代码
简而言之,你仍然须要把工做流当成是异步的,而后尝试使用 await
去写同步代码。在更加复杂的工做流中,直接使用 promise
可能更方便。
结合 promises
,一个异步函数只有两个可能的返回值:resolve值
和reject值
,而后咱们可使用 .then()
处理正常状况、.catch()
处理异常状况。可是 async/await
的错误处理就须要点技巧了。
try...catch
最多见(也是我推荐)的方法就是使用 try..catch
。当 await
一个操做时,操做中任何 reject值
都会看成异常抛出。见代码:
class BookModel {
fetchAll() {
return new Promise((resolve, reject) => {
window.setTimeout(() => { reject({'error': 400}) }, 1000);
});
}
}
// async/await
async getBooksByAuthorWithAwait(authorId) {
try {
const books = await bookModel.fetchAll();
} catch (error) {
console.log(error); // { "error": 400 }
}
复制代码
输出的错误对象正是 reject值
。捕获异常以后,咱们可使用以下方法处理它们:
catch
代码块不使用 return
语句等同于 return undefined;
,固然这也算是个正常值)。throw error
,这样容许你在 async getBooksByAuthorWithAwait()
函数上使用 promise
链式操做(即:getBooksByAuthorWithAwait().then(...).catch(error => ...)
);或者使用 Error
对象包装你的错误对象,如 throw new Error(error)
,这样在控制台查看错误时,你能够看到完整的堆栈记录。reject
错误对象,如 return Promise.reject(error)
。这等同于第一种作法,因此不推荐。使用 try...catch
的好处以下:
Java
或 C++
编程语言经历,理解起来不费事。try...catch
代码块中你能够在 try
代码块包裹多行 await
语句,而且若是前置错误处理没有必要的话,你能够在一个地方(即 catch
代码块)处理错误。这个方案仍然有它的瑕疵,try...catch
能够捕获代码块内的全部错误,包括那些不被 promises
捕获的错误。见代码:
class BookModel {
fetchAll() {
cb(); // `cb` 由于没有被定义全部会致使异常
return fetch('/books');
}
}
try {
bookModel.fetchAll();
} catch(error) {
console.log(error); // 这里打印 "cb is not defined"
}
复制代码
运行这段代码,你会在控制台获得 ReferenceError: cb is not defined
黑色字体输出信息。你要知道,这里的错误是经过 console.log()
输出的,并非 JS
自己抛出(JS
抛出错误是红色字体)。有时这会很致命:若是 BookModel
被其它一些函数调用深深嵌套、包裹,其中一个调用吞并异常,那么想找到例子中的这种错误就会变得极其困难。
受 Go
语言启发,另外一种处理错误的方法就是容许 async
函数返回异常
和结果
两个值(请参阅 How to write async await without try-catch blocks in Javascript),即你能够这样使用 async
函数:
[err, user] = await to(UserModel.findById(1));
复制代码
我我的不建议使用这种实现,由于它把 Go
语言的风格带到了 JS
,这让我感受很不天然,可是个别状况下,使用它是极其合适的。
.catch()
最后一个方法就是继续使用 .catch()
。
回想一下 await
的做用:它等待 promise
完成工做,也请记住 promise.catch()
也会返回一个 promise
!因此咱们能够这些处理错误:
// 若是发生异常,可是 catch 语句没有显示返回,那么 books === undefined
let books = await bookModel.fetchAll()
.catch((error) => { console.log(error); });
复制代码
这个实现有两个瑕疵:
promise
和 async
的混合函数。你须要理解 promise
才能读懂它。ES7
的 async/await
特性对 JS
异步编程是个巨大的改进。它让代码可读性更好、更方便调试。可是想要正确的使用他们,你必须完全了解 promise
。由于它只是个语法糖,它依赖的技术仍然是 promise
。