当前位置: 首页 > Web前端 > HTML5

【翻译】async-await应该知道的

时间:2023-04-05 18:56:51 HTML5

原文地址:https://hackernoon.com/javasc...原作者:CharleeLi翻译作者:Xixi20160512async/await是在ES7版本中引入的,它是针对JavaScript的这是.中异步编程的巨大改进。它允许我们在不阻塞主线程的情况下以同步方式处理异步进程。不过,要用好这个功能,可能还需要动动脑筋。在本文中,我们将从不同角度探索async/await,并展示如何正确有效地使用它们。async/await的优点async/await给我们带来的最大好处之一就是同步编程风格。让我们看一个例子://async/awaitasyncgetBooksByAuthorWithAwait(authorId){constbooks=awaitbookModel.fetchAll();返回books.filter(b=>b.authorId===authorId);}//promisegetBooksByAuthorWithPromise(authorId){returnbookModel.fetchAll().then(books=>books.filter(b=>b.authorId===authorId));显然,async/await版本比promise版本更容易理解。如果省略await关键字,这段代码看起来就像任何其他同步语言(如Python)。不仅仅是为了可读性,async/await有原生浏览器支持。截至今天,所有主流浏览器都支持异步功能。所有主流浏览器都支持异步功能。(图片来源:https://caniuse.com/)原生支持意味着您无需编译代码。更重要的是,这将有助于调试。当您在async方法的入口处放置断点并进入await行时,您会看到调试器在执行bookModel.fetchAll()函数之前等待一段时间,然后再进入下一步.filter行!与promise示例相比,这要容易得多,因为您必须在.filter行上放置另一个断点。调试异步函数。调试器将等待await行完成,然后再转到下一行。另一个不太明显的好处是async关键字。它声明getBooksByAuthorWithAwait()方法返回一个承诺,因此调用者可以安全地调用getBooksByAuthorWithAwait().then(...)或awaitgetBooksByAuthorWithAwait()。看看这个例子(不好的做法):getBooksByAuthorWithPromise(authorId){if(!authorId){returnnull;}returnbookModel.fetchAll().then(books=>books.filter(b=>b.authorId===authorId));}在上面的代码中,getBooksByAuthorWithPromise可能会返回一个promise(通常)或null(特别),当返回null时,调用者无法安全地调用.then()。当使用async声明时,这个问题就不会存在了。Async/await可能会产生误导有些文章将async/await与Promises进行比较,同时说它是JavaScript中异步编程发展的下一代解决方案,我不同意这种说法。Async/await是一种改进,但它只是一种语法糖,不会彻底改变我们的编程风格。本质上,异步函数仍然是promises。您必须理解promises才能正确使用异步函数,更糟糕的是,大多数时候您必须同时使用promises和异步函数。考虑上面示例中使用的getBooksByAuthorWithAwait()和getBooksByAuthorWithPromises()。请注意,它们不仅具有相同的功能,而且具有相同的界面。这意味着如果您直接调用getBooksByAuthorWithAwait(),将返回一个承诺。当然,这不是坏事。只有await给人一种感觉,“太棒了,这个可以把异步函数转成同步函数”,这是错误的。Async/await的陷阱那么在使用async/await时你会犯什么错误呢?以下是一些更常见的示例。过于线性虽然await可以让你的代码看起来像同步代码,但必须记住,这些代码仍然是以异步方式执行的,注意不要让代码过于线性。asyncgetBooksAndAuthor(authorId){constbooks=awaitbookModel.fetchAll();constauthor=awaitauthorModel.fetch(authorId);return{author,books:books.filter(book=>book.authorId===authorId),};}这段代码在逻辑上看起来是正确的。然而是不正确的。awaitbookModel.fetchAll()将等待fetchAll()完成。然后awaitauthorModel.fetch(authorId)将被执行。请注意authorModel.fetch(authorId)不依赖于bookModel.fetchAll()的结果,事实上它们可以并行执行。但是由于使用了await,这两个调用变成了串行,总耗时会远远超过并行方式。这是正确的用法:asyncgetBooksAndAuthor(authorId){constbookPromise=bookModel.fetchAll();constauthorPromise=authorModel.fetch(authorId);constbook=awaitbookPromise;constauthor=awaitauthorPromise;return{author,books:books.filter(book=>book.authorId===authorId),};}或者在更复杂的情况下,如果你想顺序请求列表的内容,你必须依赖promises:asyncgetAuthors(authorIds){//错误,这将导致顺序调用//constauthors=_.map(//authorIds,//id=>awaitauthorModel.fetch(id));//正确constpromises=_.map(authorIds,id=>authorModel.fetch(id));constauthors=awaitPromise.all(promises);}总之,你得把这个工作流想成是异步的,然后尝试用await以同步的方式写代码。复杂流程下,直接使用promises可能更简单。在使用promises进行错误处理的情况下,异步函数返回两个可能的值:resolved和rejected。我们可以使用.then()来处理正常情况,使用.catch()来处理异常情况。然而,使用async/await时,异常处理可能有点棘手。处理try...catch的最标准(也是推荐的)方法是使用try...catch表达式。在等待函数调用时,任何被拒绝的值都会作为异常抛出。这是一个例子:classBookModel{fetchAll(){returnnewPromise((resolve,reject)=>{window.setTimeout(()=>{reject({'error':400})},1000);});}}//async/awaitasyncgetBooksByAuthorWithAwait(authorId){try{constbooks=awaitbookModel.fetchAll();}赶上(错误){console.log(错误);//{"error":400}}}被传递捕获到的错误就是rejected的值。当我们捕获到这个异常后,我们有很多种处理方法:处理异常,然后返回一个正常的值。(在catch块中不使用return表达式等同于使用returnundefined;同时,返回值仍然是解析后的值。)如果希望调用者处理,则抛出该异常。您可以只抛出原始错误对象,例如抛出错误;,它允许您使用异步getBooksByAuthorWithAwait()方法链接承诺(例如,您仍然可以执行getBooksByAuthorWithAwait().then(...).catch(error=>...)之类的事情);或者,您可以用Error对象包装Error对象,例如thrownewError(error),它会在控制台中显示所有调用堆栈记录。使用Reject,比如returnPromise.reject(error),这个方法相当于throwerror,所以不推荐使用这个方法。使用try...catch的优点如下:简单、传统。只要您有使用其他语言(如C++或Java)的经验,就应该不会有任何困难来理解这是如何处理的。如果不需要在每个步骤进行错误处理,您可以将多个await调用包装在一个try...catch块中以集中所有错误处理。这种方法有一个缺陷。由于try...catch将捕获此块中的所有异常,因此也将捕获通常不会被promises捕获的其他一些异常。考虑这个例子:classBookModel{fetchAll(){cb();//注意`cb`是未定义的,会导致异常returnfetch('/books');}}try{bookModel.fetchAll();}catch(error){console.log(error);//Thiswillprint"cbisnotdefined"}执行这段代码,你会在控制台中得到一个错误:ReferenceError:cbisnotdefined,thetextisblack。此错误由console.log()而不是JavaScript本身打印。有时这将是致命的:如果BookModel被一系列函数调用深深包围,同时其中一个调用处理错误,那么将很难像这样找到错误。让一个函数同时返回两个值的方式以及错误处理的方式是受到了Go语言的启发。它允许异步函数返回假值和正常值。可以从下面的博客中得到更详细的介绍:【如何在JavascriptES7中编写没有try-catch块的asyncawaitAsync/await让我们开发者可以写出看似同步的异步JS代码。在当前的JS版本中,我们...blog.grossman.io](https://blog.grossman.io/how-...简而言之,您可以像这样使用异步函数:[err,user]=awaitto(UserModel.findById(1));我个人不喜欢这种方式,因为它把Go语言的编程风格带到了JavaScript中,这是不自然的,但在某些情况下这种方式是有用的。使用.catch我将介绍最后的处理方法还是用.catch()。回想一下await的功能:它等待一个promise来完成它的任务。再回想一下promise.catch()也返回一个promise!所以我们可以这样做like这是错误处理的处理方式://books===undefinediferrorhappens//因为在catch语句中没有返回任何内容letbooks=awaitbookModel.fetchAll().catch((error)=>{console.log(error);});这种方法有两个次要问题:它混合了promises和异步函数。你仍然需要了解promises是如何工作的才能阅读它。错误处理先于正常流程,这不太直观。结束语ES7中引入的async/await关键字无疑是对JavaScript异步编程的极大增强。它可以使代码更易于阅读和调试。但是,为了正确使用它们,必须充分理解promises,因为它们只是语法糖,底层技术仍然是promises。希望这篇文章能给你一些关于async/await的启发,帮助你避免一些常见的错误。感谢阅读,喜欢请点个赞。