博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
使用图解和例子解释Await和Async
阅读量:5875 次
发布时间:2019-06-19

本文共 7097 字,大约阅读时间需要 23 分钟。

简介

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

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

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

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方法传入回调函数。

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

clipboard.png

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 callconst 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行)。

下图描述了计算流程:

clipboard.png

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

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

// Make the first callconst 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 functionasync 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 functionsolution().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是异步的,它也将与其调用者并行运行

clipboard.png

函数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,并将诉诸于vanillaPromise

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

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

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

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

clipboard.png

翻译自

转载地址:http://wpkix.baihongyu.com/

你可能感兴趣的文章
Lua For Windows 环境配置及使sciTE支持中文
查看>>
找回密码测试注意
查看>>
步进器和分组按钮
查看>>
php 字符串转成二维数组
查看>>
Linux通配符和正则表达式的异同
查看>>
GCD 使用中需要注意的细节以及容易混淆的知识点
查看>>
CCNA学习笔记--VLAN划分及vlan间通信
查看>>
李新海:不甘心,就让自己更忙一点
查看>>
10.WinFor练习--自动播放图片小程序
查看>>
Java - 在WebService中使用Client调用三方的RestAPI
查看>>
Linux 装 Python3.6
查看>>
字典及datetime模块
查看>>
常见的web服务器
查看>>
Android操作系统漏洞允许***者跟踪用户位置
查看>>
一般人不知道的SEO术语!!!
查看>>
Java 本地环境设置
查看>>
Centos yum 安装zabbix3.4
查看>>
Java日期时间操作源码示例大全
查看>>
TCP三次握手
查看>>
初学者最常问的几个问题,别问了看这里!
查看>>