龙空技术网

前端的你,应该会使用 async await 了

月漫湾 1618

前言:

而今各位老铁们对“js解决回调地狱”可能比较重视,朋友们都想要分析一些“js解决回调地狱”的相关知识。那么小编也在网摘上网罗了一些对于“js解决回调地狱””的相关内容,希望朋友们能喜欢,我们快快来了解一下吧!

前端的你,应该会使用 async await 了!

ES7 版本开始引入 async/await 特性,对于异步编程而言是一个巨大的提升。以同步的方式处理异步的流程,同时不阻塞主线程

async/await 的优点

async/await避免了js的回调地狱问题。让我们看一个例子:

async function showAvatar() { // read our JSON let response = await fetch('/article/promise-chaining/user.json') let user = await response.json()  // read github user let githubResponse = await fetch(`{user.name}`) let githubUser = await githubResponse.json()  // 展示头像 let img = document.createElement('img') img.src = githubUser.avatar_url img.className = 'promise-avatar-example' documenmt.body.append(img)  // 等待3s await new Promise((resolve, reject) => { setTimeout(resolve, 3000) })  img.remove()  return githubUser}

很明显,async/await 比 promise 更加易读。

async/await 有浏览器的原生支持。

所有主流浏览器都支持 async 函数

原生支持意味着你不需要编译代码。更重要的是,这个将有助于调试。断点会在await行停止,直到await后面的函数执行完成,并获取到数据为止。

以下部分引用于:

实质上,async 函数仍然是 promise。你必须理解 promises 之后才能正确的使用 async 函数,更糟糕的是,大多数情况下你必须同时使用 promises 和 async 函数。

思考一下上面例子中使用到 的 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) {  // WRONG, this will cause sequential calls  // const authors = _.map(  // authorIds,  // id => await authorModel.fetch(id));// CORRECT  const promises = _.map(authorIds, id => authorModel.fetch(id));  const authors = await Promise.all(promises);}复制代码

简而言之,你必须把这个工作流程看成是异步的,然后再尝试使用 await 以同步的方式去编写代码。在复杂的流程下面,直接使用 promises 可能会更简单。

错误处理

使用 promises 的情况下,一个异步函数会返回两种可能的值:resolved 和 rejected。我们可以使用 .then() 来处理正常的情况 .catch() 处理异常情况。然而对于 async/await 来说,异常处理可能会有点诡异。

try...catch

最标准的(也是我推荐的)处理方式是使用 try...catch 表达式。当 await 一个函数调用的时候,任何 rejected 的值都会以异常的形式抛出来。这里有个例子:

class BookModel {  fetchAll() {  return new Promise((resolve, reject) => {  window.setTimeout(() => {  reject({'error': 400})  }, 1000);  });  }}// async/awaitasync getBooksByAuthorWithAwait(authorId) { try {  const books = await bookModel.fetchAll(); } catch (error) {  console.log(error); // { "error": 400 } }}复制代码

被捕获的错误就是 rejected 的值。在我们捕获这个异常之后,我们有很多方式来处理它:

处理掉这个异常,然后返回一个正常的值。(没有在 catch 块中使用任何 return 表达式等价于使用 return undefined ;同时,返回的仍是一个 resolved 的值。)抛出这个异常,如果你希望调用者去处理它。你可以直接抛出原始的错误对象,例如 throw error; ,这种方式允许你以 promise 链式的方式使用 async getBooksByAuthorWithAwait() 方法(列如,你仍然可以像 getBooksByAuthorWithAwait().then(...).catch(error => ...) 这样调用它);或者,你可以使用 Error 对象包装错误对象,例如, throw new Error(error) ,使用这种方式可以在控制台中展示所有的调用栈记录。使用 Reject,例如, return Promise.reject(error) ,这个方式等价于 throw error ,因此不推荐使用这种方式。

使用 try...catch 的优点有以下这些:

简单,传统。只要你有其他语言的经验,例如 C++ 或 Java,理解这种处理方式将不会有任何困难。你可以将多个 await 调用包装在一个 try...catch 块中来集中处理所有错误,如果每一步的错误处理非必要的话。

这种处理方式有一个缺陷。由于 try...catch 将会捕获这个代码块中的所有异常,一些其他通常不会被 promises 捕获的异常也会被捕获住。考虑一下这个例子:

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 函数同时返回错误的值和正常的值。可以从下面这个博客中了解到更详细的的介绍:

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 函数:

[err, user] = await to(UserModel.findById(1));复制代码

我个人并不喜欢这种处理方式,因为它把 Go 语言的编程风格带到了 JavaScript 中,这样显得不自然,但是在某些情况下这种方式会很有用。

使用 .catch

我要介绍的最后一种处理方式是仍然使用 .catch()。

回忆一下 await 的功能:它会等待一个 promise 完成它的任务。同时请回忆一下, promise.catch() 也会返回一个 promise!因此我们可以像下面这样处理错误处理的方式:

// books === undefined if error happens,// since nothing returned in the catch statementlet books = await bookModel.fetchAll() 	.catch((error) => {  console.log(error);  });复制代码

这种处理方式有两个次要的问题:

这种方式混合了 promises 和 async 函数。你仍然需要理解 promises 的运行原理之后才能读懂它。错误处理在正常流程之前,这样是不太直观的。

结论

在 ES7 中引入的 async/await 关键字无疑是对 JavaScript 异步编程的一大加强。它能够把代码变得更易于阅读和调试。然后,为了正确的使用它们,必须要完全理解 promises,因为它们不过是语法糖,底层的技术仍然是 promises。

标签: #js解决回调地狱