【译】图与例解读Async/Await

2023-05-28 0 328

书名:Await and Async Explained with Diagrams and Examples原作:Nikolay翻译者:安秦

到了那时async/await早已并非甚么新小东西了,我对个人

简述

JavaScript ES7的async/await句法让触发器promise操作方式出来更方便快捷。假如你须要从

这篇讲义以图象与单纯范例来阐释JS async/await的句法与运转分子结构。

在深入细致以后,他们先单纯简述呵呵promise,假如对这点基本概念有自信心,大可另行埃唐佩县。

Promise

在JS的当今世界里,两个promise抽象化抒发两个非堵塞(堵塞指两个各项任务已经开始后,要等候该各项任务拒绝执行结论造成后才继续拒绝执行先期各项任务)的触发器业务流程,近似于Java的Futrue或是C#的Task。

Promise最众所周知的采用情景是互联网或其它I/O操作方式(如加载两个文档或是发送两个HTTP允诺)。与堵塞住现阶段的拒绝执行“缓存”,他们能造成两个触发器的promise,接着用then方式来附带两个反弹,用作拒绝执行该promise顺利完成之孔利耶做的事。反弹另一方面也能回到两个promise,这般我就能将数个promise串连。

为方便快捷表明,假设先期大部份的范例都早已导入了request-promise库:

var rp = require(request-promise);

接着他们就能这般推送两个单纯的HTTP GET允诺并赢得两个promisecodice:

const promise = rp(http://example.com/)

那时来看个范例:

console.log(Starting Execution); const promise = rp(http://example.com/); promise.then(result => console.log(result)); console.log(“Cant know if promise has finished yet…”);

他们在第3行造成了两个promise,接着在第4行附上了两个反弹函数。回到的promise是触发器的,所以当拒绝执行的第6行的时候,他们无法确定这个promise有没有顺利完成,多次拒绝执行可能有不同的结论(翻译者:浏览器里拒绝执行多少次,这里promise都会是未顺利完成状态)。概括来说,promise后的代码跟promise另一方面是并发的(翻译者:对这句话有异议者参见本文最后一节的并发表明)。

并不存在一种方式能让现阶段的拒绝执行业务流程堵塞直到promise顺利完成,这一点与Java的Futrue.get相异。JS里,他们无法直接原地等promise顺利完成,唯一能用作提前计划promise顺利完成后的拒绝执行逻辑的方式就是通过then附带反弹函数。

下面的图象描绘了上面代码范例的拒绝执行过程:

【译】图与例解读Async/Await
Promise的拒绝执行过程,调用“缓存”无法直接等候promise结论。唯一规划promise后逻辑的方式是采用then方式附带两个反弹函数。

通过then 附带的反弹函数只会在promise成功是被触发,假如失败了(比如互联网异常),这个反弹不会拒绝执行,处理错误须要通过catch方式:

rp(http://example.com/). then(() => console.log(Success)). catch(e => console.log(`Failed: ${e}`))

最后,为了方便快捷试验功能,他们能直接创建一些“假想”的promise,采用Promise.resolve生成会直接成功或失败的promise结论:

const success = Promise.resolve(Resolved); // Will print “Successful result: Resolved” success. then(result => console.log(`Successful result:${result}`)). catch(e => console.log(`Failed with: ${e}`)) const fail = Promise.reject(Err); // Will print “Failed with: Err”fail. then(result => console.log(`Successful result: ${result}`)). catch(e => console.log(`Failed with: ${e}`))

欲看更详细的Promise讲义,推荐阅读这篇文章

问题——组合数个Promise

只采用两个单次的promise非常单纯。然而假如他们须要编写一个非常复杂了触发器逻辑,他们可能须要将若干个promise组合出来。写许多的then语句以及匿名函数很容易失控。

比如,他们须要实现以下逻辑:

发起两个HTTP允诺,等候结论并将其输出再发起两个并发的HTTP允诺当两个允诺都顺利完成时,一起输出他们

下面的代码演示如何达到这个要求:

// Make the first call const call1Promise = rp(http://example.com/); call1Promise.then(result1 => { // Executes after the first request has finished console.log(result1); const call2Promise = rp(http://example.com/); const call3Promise = rp(http://example.com/); return Promise.all([call2Promise, call3Promise]); }).then(arr => { // Executes after both promises have finished console.log(arr[0]); console.log(arr[1]); })

他们先呼叫第一次HTTP允诺,接着预备两个在它顺利完成时拒绝执行的反弹(第1-3行)。在反弹里,他们为另外两次允诺制造了promise(第8-9行)。这两个promise并发运转,他们须要计划两个在两个都顺利完成时拒绝执行的反弹,于是,他们通过Promise.all(第11行)来讲他们合并。这第两个反弹的codice是两个promise,他们再添加两个then来输出结论(第12-16行)。

以下图标描绘这个计算过程:

【译】图与例解读Async/Await
将promise组合的计算过程。采用“Promise.all”将两个并发的promise合并成两个。

为了两个单纯的范例,他们最终写了两个then反弹以及两个Promise.all来同步两个并发promise。假如他们还想再多做几个触发器操作方式或是添加一些错误处理会怎样?这种实现方案最终很容变为纠缠成一坨的then、Promise.all以及反弹匿名函数。

Async函数

两个async函数是定义会回到promise的函数的简便写法。

比如,以下两个定义是等效的:

function f() { return Promise.resolve(TEST); } // asyncF is equivalent to f!async function asyncF() { return TEST; }

相似地,会抛出错误的async函数等效于回到将失败的promise的函数:

function f() { return Promise.reject(Error); } // asyncF is equivalent to f! async function asyncF() { throw Error; }

Await

以前,当他们造成两个promise,他们无法同步地等候它顺利完成,他们只能通过then注册两个反弹函数。不允许直接等候两个promise是为了鼓励开发者写非堵塞的代码,不然开发者会更乐意写堵塞的代码,因为这样比promise和反弹单纯。

然而,为了同步数个promise,他们须要它们互相等候,换句话说,假如两个操作方式本身就是触发器的(比如,用promise包装的),它应该具备能力等候另两个触发器操作方式先顺利完成。但是JS解释器如何知道两个操作方式是并非在两个promise里的?

答案就是async关键字,大部份的async函数一定会回到两个promise。所以,JS解释器也能确信async函数里操作方式是用promise包装的触发器过程。于是也就能允许它等候其它promise。

键入await关键字,它只能在async函数内采用,让他们能等候两个promise。假如在async函数外采用promise,他们依然须要采用then和反弹函数:

async function f(){ // response will evaluate as the resolved value of the promise const response = await rp(http://example.com/); console.log(response); } // We cant use await outside of async function.// We need to use then callbacks …. f().then(() => console.log(Finished));

那时他们来看看他们能如何解决以后提到的问题:

// Encapsulate the solution in an async function async function solution() { // Wait for the first HTTP call and print the result console.log(await rp(http://example.com/)); // Spawn the HTTP calls without waiting for them – run them concurrently const call2Promise = rp(http://example.com/); // Does not wait! const call3Promise = rp(http://example.com/); // Does not wait! // After they are both spawn – wait for both of them const response2 = await call2Promise; const response3 = await call3Promise; console.log(response2); console.log(response3); } // Call the async function solution().then(() => console.log(Finished));

上面的片段,我们将逻辑分装在两个async函数里。这样他们就能直接对promise采用await了,也就规避了写then反弹。最后他们调用这个async函数,接着按照普通的方式采用回到的promise。

要注意的是,在第两个范例里(没有async/await),后面两个promise是并发的。所以他们在第7-8行也是这般,接着直到11-12行才用await来等候两个promise都顺利完成。这后,他们能确信两个promise都早已顺利完成(与以后Promise.all(…).then(…)类似)。

计算业务流程跟以后的图象描绘的一样,但是代码变得更加已读与直白。

事实上,async/await其实会翻译成promise与then反弹(翻译者:babel其实是翻译成generator句法,再通过类似co的函数运转,co内部运转机制离不开promise)。每次他们采用await,解释器会创建两个promise接着把async函数的先期代码放到then反弹里。

他们来看看以下的范例:

async function f() { console.log(Starting F); const result = await rp(http://example.com/); console.log(result); }

f函数的内在运转过程如下图所描绘。因为f标记了async,它会与它的调用者“并发”:

【译】图与例解读Async/Await
await的计算逻辑

函数f启动并造成两个promise。在这一刻,函数剩下的部分都会被封装到两个反弹函数里,并被计划在promise顺利完成后拒绝执行。

错误处理

在以后的范例里,他们大多假设promise会成功,接着await两个promise的回到值。假如他们等候的promise失败了,会在async函数里造成两个异常,他们能采用标准的try/catch来处理它:

async function f() { try { const promiseResult = await Promise.reject(Error); } catch (e){ console.log(e); } }

假如async函数不处理这个异常,不管是这异常是因为promise是被reject了还是其它的bug,这个函数都会回到两个被reject掉的promise:

async function f() { // Throws an exception const promiseResult = await Promise.reject(Error); } // Will print “Error” f(). then(() => console.log(Success)). catch(err => console.log(err)) async function g() { throw “Error”; } // Will print “Error” g(). then(() => console.log(Success)). catch(err => console.log(err))

这就让他们能采用熟悉的方式来处理错误。

扩展表明

async/await是两个对promise进行补充的句法部件,它能让他们写更少的重复代码来采用promise。然而,async/await并不能彻底取代普通的promise。比如,假如他们在两个普通的函数或是全局作用域里采用两个async函数,他们无法采用await,也就只能求助于原始的promise用法:

async function fAsync() { // actual return value is Promise.resolve(5) return 5; } // cant call “await fAsync()”. Need to use then/catchfAsync().then(r => console.log(`result is ${r}`));

我通常会把大部分的触发器逻辑封装在两个或少量几个async函数里,接着在非async的代码区域里采用,这样就能尽量减少书写then或catch反弹。

async / await是让promise用出来更简洁的句法糖。大部份的async / await都能用普通的promise来实现。大部份总结来说,这只是个代码样式与简洁的问题。

学院派的人会指出,并发与并行是有区别的(翻译者:所以前文都是说并发,而非并行)。参见Rob Pike的讲话或是我以后的博文。并发是组合数个独立过程来一起工作,并行是数个过程同时拒绝执行。并发是体那时应用的结构设计,并行是实际拒绝执行的方式。

他们来看看两个多缓存应用的范例。将应用分割成数个缓存是该应用并发模型的定义,将这些缓存放到可用的cpu核心上拒绝执行是确立它的并行。两个并发的系统也能在两个单核处理器上正常运转,但这种情况并并非并行。

【译】图与例解读Async/Await
并发(concurrent)与并行(parallel)

以这种方式理解,promise能将两个程序分解成数个并发的模块,它们或许,也可能并不会并行拒绝执行。JS是否并行拒绝执行要看解释器另一方面的实现。比如,NodeJS是单缓存的,假如两个promise里有大量的CPU操作方式(非I/O操作方式),你可能感受不到太多并行。然而假如你用像nashorn这样的工具把代码编译成java字节码,理论上你能把繁重的CPU操作方式放到其它内核上来赢得平行效果。于是在我的观点中,promise(不管是裸的还是有async/await)只是作用作定义JS应用的并发模型(而非确定逻辑是否会并行运转)。

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务