首页 » 漏洞 » 使用图解和例子解释Await和Async

使用图解和例子解释Await和Async

 

简介

JavaScript ES7中的 async/await 语法使得异步Promise变得更加容易。 如果您需要以某种顺序从多个数据库或API异步获取数据,则可以使用promise和回调构成的面条式的代码。 async/await 构造允许我们更简洁地表达这种逻辑且代码更易读和可维护。

本教程将使用图表和简单示例来解释JavaScript async/await 语法和语义。

在我们开始之前,让我们从一个 Promise 的简要概述开始。 如果您已经了解了JS Promise ,请随时跳过本节。

Promises

在JavaScript中, Promises 代表非阻塞异步执行的抽象。 如果了解其他语言的话,JSPromise与Java的 Future 或C#的 Task 类似。

Promises 通常用于网络和I/O操作 - 例如从文件读取或发出HTTP请求。 如果不需要阻塞当前的“线程”执行,我们可以产生一个异步 Promises ,并使用then方法来传入一个回调函数,它在promise完成时将被触发。 回调函数本身可以返回 Promise ,因此我们可以链式调用 Promise

为了简单起见,在所有示例中,我们假设 request-promise 已经安装并可以像下面这样子加载:

var rp = require('request-promise');

现在我们可以做一个简单的HTTP GET请求,返回一个 Promise

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

现在,我们来看一个例子:

console.log('Starting Execution');  const promise = rp('http://example.com/'); promise.then(result => console.log(result));  console.log("Can't know if promise has finished yet...");

我们在 第3行 产生了一个新的 Promise ,然后在 第4行 附加一个回调函数。 Promise 是异步的,所以当我们到达 第6行 时,我们不知道 Promise 是否已经完成。 如果我们多次运行代码,我们可能会每次得到不同的结果。 更确切地说,任何承诺之后的代码都是与 Promise 同时运行的。

Promise 完成之前,我们没有任何合理的理由阻止当前的操作顺序 。 这与Java的 Future.get 不同,它允许我们阻止当前线程,直到将来完成。 在JavaScript中, 我们不能等待 Promise 完成 。 在 Promise 完成之后执行代码的唯一方法是通过 then 方法传入回调函数。

下图描绘了该示例的计算过程:

使用图解和例子解释Await和Async

Promise 的计算过程。 调用“线程”不能等待 Promise 。 在 Promise 之后执行代码的唯一方法是通过 then 方法指定回调函数。

只有当 Promise 成功时,回调函数才能执行。 如果它失败(例如由于网络错误),回调函数将不会执行。 为了处理失败的 Promise ,你可以通过 catch 传入另一个回调:

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

最后,为了测试的目的,我们可以轻松地创建使用 Promise.resolvePromise.reject 方法创建成功或失败的 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 和匿名回调可以很容易失去对代码的控制。

例如,假设我们需要编写一个程序:

  1. 发起http请求,等待完成,打印结果;
  2. 返回之后进行其他两个HTTP的并行调用;
  3. 当它们都完成时,打印结果。

以下代码段演示了如何完成此操作:

// 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/');      const combinedPromise = Promise.all([call2Promise, call3Promise]);     combinedPromise.then(arr => {         // Executes after both promises have finished         console.log(arr[0]);         console.log(arr[1]);     }) })

我们首先发起了第一个HTTP请求,并在其完成时运行回调函数(第1-3行)。 在回调中,我们为后续的HTTP请求产生了两个 Promise (第8-9行)。 这两个 Promise 同时运行,我们需要安排一个回调,在它们都完成时调用。 因此,我们需要通过 Promise.all (第11行)将它们组合成一个单一的 Promise ,当它们完成时,它们就可以正确调用。 然后我们传入了另一个打印结果的回调(第14-15行)。

下图描述了计算流程:

使用图解和例子解释Await和Async

对于这样一个简单的例子,我们最终得到了2个嵌套的回调函数,并且必须使用Promise.all来同步并发 Promise 。 如果我们不得不再运行一些异步操作或添加错误处理怎么办? 这种方法可以很容易地改写成用 Promise.all 和多个 then 连接起来的链式面条代码。

我们可以重写上面的例子来使用“promise chaining”,如下所示:

// 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]); })

这样可读性更高一些,尽管我们仍然需要链接两个回调函数并使用 Promise.all

Async 函数

Async函数是返回 Promise 函数的简写。

例如,以下定义是等价的:

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

类似地,抛出异常的Async函数等效于返回 reject Promise 的函数:

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

Await

当我们产生承诺时,我们无法同步等待完成。 我们只能通过一个回调。 不允许等待承诺鼓励开发非阻塞代码。 否则,开发人员将被诱惑执行封锁操作,因为它比使用承诺和回调更容易。

当我们创建 Promise 时,我们无法同步等待完成。 我们只能通过一个回调。 不允许等待 Promise ,鼓励开发非阻塞代码。 否则,开发人员将更容易使用锁定当前线程的操作,因为它比使用 Promise 和回调更容易。

然而,为了同步 Promise ,我们需要允许他们相互等待。 换句话说,如果操作是异步的(即封装在 Promise 中),则应该能够等待另一个异步操作完成。 但是JavaScript解释器如何知道一个操作是否在 Promise 中运行?

答案是在 async 关键字。 每个 async 函数都返回一个 Promise 。 因此,JavaScript解释器知道 async 函数中的所有操作都将被封装在 Promise 中并异步运行。 所以可以让他们等待其他的 Promise 完成之后再继续执行。

当我们使用await关键字。 它只能用于 async 功能,并允许我们同步等待 Promise 。 如果我们在 async 函数之外使用 Promise ,我们仍然需要使用回调函数:

async function f(){     // response will evaluate as the resolved value of the promise     const response = await rp('http://example.com/');     console.log(response); }  // We can't 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 ,从而避免了回调的需要。 最后,我们调用 async 函数,该函数只是产生一个封装了调用其他 Promise 的逻辑的 Promise

事实上,在第一个例子中(没有 async/await ),这些 Promise 将会并行开始。 在这种情况下,我们做同样的(7-8行)。 请注意,直到 第11-12行 ,当我们使用了 await ,直到两个 Promise 都已经完成为止。 之后,我们知道这两个 Promise 都已经完成了(类似于前面的例子中使用 Promise.all (...)然后(...))。

实际计算过程等同于上一节所述的过程。 然而,代码更加可读和直观。

在引导下, async/await 实际上转化为 Promise ,然后回调。 换句话说,它是使用 Promise 的语法糖。 每次我们等待,解释器产生一个 Promise ,并将其余的操作从异步功能放在一个回调。

我们来考虑下面的例子:

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

f函数的基础计算过程如下所示。 由于f是异步的,它也将与其调用者并行运行

使用图解和例子解释Await和Async

函数f启动并产生 Promise 。 在那一刻,函数的其余部分被封装在一个回调函数中,并且在 Promise 完成之后计划执行。

错误处理

在前面的大多数例子中,我们假设 Promise 成功执行了。 因此,等待 Promise 返回值。 如果我们等待失败的 Promise ,这将导致异步功能中的异常。 我们可以使用标准的 try/catch 来处理它:

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

如果 async 函数不处理异常,无论是由拒绝 Promise 还是其他错误引起的,都将返回被拒绝的 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))

这通过已知的异常处理机制使我们方便地处理被拒绝的 Promise

讨论

Async/await 是一种对 Promise 的语言上的补充。 它允许我们以较少的样板来使用 Promise 。 但是, Async/await 不能取代纯粹 Promise 的需要。 例如,如果我们从正常函数或全局范围调用 Async 函数,我们将无法使用 await ,并将诉诸于vanilla Promise

async function fAsync() {     // actual return value is Promise.resolve(5)     return 5; }  // can't call "await fAsync()". Need to use then/catch fAsync().then(r => console.log(`result is ${r}`));

我通常会尝试将大多数异步逻辑封装在一个或几个异步函数中,这是从非异步代码中调用的。 这最大限度地减少了我需要编写的 try/catch 回调的数量。

Async/await 结构是更符合 Promise 的语法糖。 每个 Async/await 结构可以用简单的 Promise 重写。 所以,这是一个风格和简洁的问题。

关注我的微信公众号,更多优质文章定时推送

使用图解和例子解释Await和Async

翻译自 http://nikgrozev.com/2017/10/...

原文链接:使用图解和例子解释Await和Async,转载请注明来源!

0