class BookModel {
fetchAll() {
cb(); // note `cb` is undefined and will result an exception
return fetch('/books');
}
}
try {
bookModel.fetchAll();
} catch(error) {
console.log(error); // This will print "cb is not defined"
}
执行这段代码你将会在控制台中得到一个错误: ReferenceError: cb is not defined ,这些文字是黑色的。这个错误是 console.log() 打印出来的而不是 JavaScript 自身。某些时候这将会是致命的:如果 BookModel 被一系列函数调用深深地封闭起来了,同时,其中某一个调用将这个错误处理掉了,这时候就很难像这样去发现这个错误了。
使函数同时返回两个值
另外一个错误处理的方式是由 Go 语言启发的。它允许 async 函数同时返回错误的值和正常的值。可以从下面这个博客中了解到更详细的的介绍:
// books === undefined if error happens,
// since nothing returned in the catch statement
let books = await bookModel.fetchAll()
.catch((error) => {
console.log(error);
});
async/await
是在 ES7 版本中引入的,它对于 JavaScript 中的异步编程而言是一个巨大的提升。它可以让我们以同步的方式处理异步的流程,同时不会阻塞主线程。但是,想要用好这一特性,可能需要动点脑筋。本文中,我们将从不同的角度探讨 async/await,同时会展示如何正确和高效的使用它们。async/await 的优点
async/await
带给我们最大的一个好处就是同步的编程风格。让我们看一个例子:很明显,
async/await
的版本比 promise 的版本更加的易于理解。如果你忽略await
关键字,这段代码看起来就像任何其他的同步式语言(比如说 Python)。不仅仅是可读性,
async/await
有浏览器的原生支持。到今天为止,所有主流浏览器都支持async
函数。所有主流浏览器都支持
async
函数。(图片来源:https://caniuse.com/)原生支持意味着你不需要编译代码。更重要的是,这个将有助于调试。当你在 async 方法的入口打一个断点并且步进到
await
这一行的时候,你将会看到调试器在bookModel.fetchAll()
这个函数执行的时候等待了一会儿,然后才会走到接下来的.filter
这一行!和 promise 的示例比较起来,这个容易多了,因为你必须在.filter
这一行再打一个断点。调试 async 函数。调试器会在 await 这一行等待执行完成然后才会移动到下一行。
另一个不那么明显的好处就是
async
关键字。它声明了getBooksByAuthorWithAwait()
方法返回的是一个 promise,因此调用者可以像getBooksByAuthorWithAwait().then(...)
或者await getBooksByAuthorWithAwait()
这样安全的调用。看一下这个例子(不好的实践):在上面的代码中,
getBooksByAuthorWithPromise
可能返回一个 promise (正常情况下)或者null
(特殊情况下),返回 null 的时候调用者不能安全的调用.then()
。使用async
进行声明的时候,这个问题就不会存在了。Async/await 可能会产生误导
一些文章把 async/await 和 Promise 进行了比较,同时说它是 JavaScript 异步编程演变过程中的下一代解决方案,对此我不敢苟同。Async/await 是一个提升,但它仅仅是一个语法糖,它将不会完全的改变我们的编程风格。
实质上,async 函数仍然是 promise。你必须理解 promises 之后才能正确的使用 async 函数,更糟糕的是,大多数情况下你必须同时使用 promises 和 async 函数。
思考一下上面例子中使用到 的
getBooksByAuthorWithAwait()
和getBooksByAuthorWithPromises()
。请注意,它们不仅是有相同的功能,同时也有相同的接口。这意味着如果你直接
getBooksByAuthorWithAwait()
的话,将会返回一个 promise。当然,这并不是一件不好的事情。只有
await
给人们的一种感觉,“很棒,这个可以将异步的函数转换成同步的函数”,这个才是错误的。Async/await 的陷阱
那么在使用
async/await
的过程中会犯哪些错误呢?这里有一些比较常见的例子。过于线性化
虽然
await
能够使你的代码看起来像同步代码一样,但是一定要记住这些代码仍然是以异步的方式执行的,注意不要使代码过于线性化。这段代码看起来逻辑上没有问题。然而是不正确的。
await bookModel.fetchAll()
将会等待fetchAll()
执行完。await authorModel.fetch(authorId)
才会被执行注意,
authorModel.fetch(authorId)
并不依赖bookModel.fetchAll()
的结果,实际上他们可以并行执行。然而,由于使用了await
这两次调用就变成了串行的了,花费的总时间将会远超并行的方式。以下是正确的使用方式:
或者更复杂的情况下,如果你想依次请求一个列表的内容,你必须依赖 promises:
简而言之,你必须把这个工作流程看成是异步的,然后再尝试使用
await
以同步的方式去编写代码。在复杂的流程下面,直接使用 promises 可能会更简单。错误处理
使用 promises 的情况下,一个异步函数会返回两种可能的值:resolved 和 rejected。我们可以使用
.then()
来处理正常的情况.catch()
处理异常情况。然而对于async/await
来说,异常处理可能会有点诡异。try...catch
最标准的(也是我推荐的)处理方式是使用
try...catch
表达式。当await
一个函数调用的时候,任何 rejected 的值都会以异常的形式抛出来。这里有个例子:被捕获的错误就是 rejected 的值。在我们捕获这个异常之后,我们有很多方式来处理它:
catch
块中使用任何return
表达式等价于使用return undefined
;同时,返回的仍是一个 resolved 的值。)throw error;
,这种方式允许你以 promise 链式的方式使用async getBooksByAuthorWithAwait()
方法(列如,你仍然可以像getBooksByAuthorWithAwait().then(...).catch(error => ...)
这样调用它);或者,你可以使用Error
对象包装错误对象,例如,throw new Error(error)
,使用这种方式可以在控制台中展示所有的调用栈记录。return Promise.reject(error)
,这个方式等价于throw error
,因此不推荐使用这种方式。使用
try...catch
的优点有以下这些:await
调用包装在一个try...catch
块中来集中处理所有错误,如果每一步的错误处理非必要的话。这种处理方式有一个缺陷。由于
try...catch
将会捕获这个代码块中的所有异常,一些其他通常不会被 promises 捕获的异常也会被捕获住。考虑一下这个例子:执行这段代码你将会在控制台中得到一个错误:
ReferenceError: cb is not defined
,这些文字是黑色的。这个错误是console.log()
打印出来的而不是 JavaScript 自身。某些时候这将会是致命的:如果BookModel
被一系列函数调用深深地封闭起来了,同时,其中某一个调用将这个错误处理掉了,这时候就很难像这样去发现这个错误了。使函数同时返回两个值
另外一个错误处理的方式是由 Go 语言启发的。它允许 async 函数同时返回错误的值和正常的值。可以从下面这个博客中了解到更详细的的介绍:
How to write async await without try-catch blocks in Javascript ES7 Async/await allows us as developers to write asynchronous JS code that look synchronous. In current JS version we…blog.grossman.io
简而言之,你能够像下面这样使用 async 函数:
我个人并不喜欢这种处理方式,因为它把 Go 语言的编程风格带到了 JavaScript 中,这样显得不自然,但是在某些情况下这种方式会很有用。
使用 .catch
我要介绍的最后一种处理方式是仍然使用
.catch()
。回忆一下
await
的功能:它会等待一个 promise 完成它的任务。同时请回忆一下,promise.catch()
也会返回一个 promise!因此我们可以像下面这样处理错误处理的方式:这种处理方式有两个次要的问题:
结论
在 ES7 中引入的
async/await
关键字无疑是对 JavaScript 异步编程的一大加强。它能够把代码变得更易于阅读和调试。然后,为了正确的使用它们,必须要完全理解 promises,因为它们不过是语法糖,底层的技术仍然是 promises。希望这篇文章能够给你一些关于
async/await
的启发,同时能够帮助你避免一些常见的错误。感谢阅读,如果喜欢的话,请为我点赞。