原文链接:Await and Async Explained with Diagrams and Examples

文章目录

  1. 简介
  2. Promise
  3. 问题:组合 Promise
  4. Async 函数
  5. Await
  6. 错误处理
  7. 讨论

简介

JavaScript ES7中的 async/await 使得协调异步 promise 变得更容易。如果你需要从多个数据库或 API 异步获取数据,则可以使用 promise 和回调函数。async / await 使我们更简洁地表达这种逻辑,并完成更易读和可维护的代码。

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

在讲解之前,我们从 promises 的简要概述开始。如果你已经了解了 JS 中的 promise,请随时跳过本节。

Promises

在 JavaScript 中,promise 代表非阻塞异步执行的抽象对象。JS中的 promise 与 Java 中的Future 或C#的Task类似,如果你了解它们的话那就很容易理解。

Promise 通常用于网络和 I/O 操作,例如读取文件或者发出 HTTP 请求。我们可以产生一个异步 promise ,并使用 then 的方法来附加一个回调函数,这个回调函数当 promise 完成时将会被触发,这种方法不会阻止当前的“线程”执行。回调函数本身可以返回 promise,使我们可以有效地链接 promises

为了容易理解,在所有示例中,我们假设 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/'); // Line 3
promise.then(result => console.log(result)); // Line 4console.log("Can't know if promise has finished yet...");

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

promise 完成之前,并没有办法阻止当前的操作顺序。 这与 Java 中的 Future.get 不同,其允许我们阻止当前线程,然后之后完成。在 JavaScript 中,我们不能等待 promise。在 promise 之后调度代码的唯一方法是通过 then 附加回调函数。

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

promise 的计算过程。呼叫“线程”不能等待 promise 。在 promise 之后调度代码的唯一方法是通过then方法指定回调函数。

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

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

最后,为了测试一下,我们可以使用Promise.resolvePromise.reject方法创建成功或失败的“虚拟”promises:

const success = Promise.resolve('Resolved');
// 将会显示 "Successful result: Resolved"
success.then(result => console.log(`Successful result: ${result}`)).catch(e => console.log(`Failed with: ${e}`))const fail = Promise.reject('Err');
// 将会显示 "Failed with: Err"
fail.then(result => console.log(`Successful result: ${result}`)).catch(e => console.log(`Failed with: ${e}`))

有关 promises 的更详细的教程,请查看这篇文章

组合 Promise

使用单一 Promise 是简单有效的。但是,当我们需要对复杂的异步逻辑进行编程时,我们可能会以组合多个 Promise。编写所有的子句和匿名回调可能很容易失控。

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

  1. 进行HTTP请求,等待完成,打印结果;
  2. 然后进行其他两个并行HTTP调用;
  3. 当它们都完成时,打印结果。 以下代码段演示了如何完成此操作:
//进行第一个调用
const call1Promise = rp('http://example.com/'); // Line 2call1Promise.then(result1 => {//在第一个请求完成后执行console.log(result1);const call2Promise = rp('http://example.com/'); // Line 8const call3Promise = rp('http://example.com/'); // Line 9return Promise.all([call2Promise, call3Promise]); // Lin 11
}).then(arr => { // Line 12//两个 promise 完成后执行console.log(arr[0]);console.log(arr[1]);
})

我们首先进行第一个 HTTP 调用,并使用回调以在其完成时运行(第1-3行)。在回调中,我们为后续的 HTTP 请求产生了两个 Promise(第8-9行)。这两个 Promise 同时运行并且我们需要安排一个回调,当它们都完成时。因此,当它们都执行完成时,我们需要通过Promise.all(第11行)将它们组合成一个单一的 Promise。回调的结果是一个 Promise,我们需要l连接另一个回调函数来打印结果(行12-16)。

下图描述了计算流程:

计算过程中的 Promise 组合。我们使用“Promise.all”将两个并发的 Promise组合成一个 Promise。

对于这样一个简单的例子,我们最终得到了 2 个回调,并且必须使用Promise.all来同步并发Promise。如果我们不得不再运行一些异步操作或添加错误处理怎么办?这种方法最终很容易崩溃于then-s,Promise.all调用和回调三者混杂在一起。

Async

异步函数是用于定义返回Promise的函数的快捷方式。 例如,以下定义是等价的:

function f() {return Promise.resolve('TEST');
}// asyncF相当于f!
async function asyncF() {return 'TEST';
}

类似地,抛出异常的异步函数等效于返回拒绝 Promise 的函数:

function f() {return Promise.reject('Error');
}// asyncF相当于f!
async function asyncF() {throw 'Error';
}

Await

当我们使用 promise 之后,我们只能通过then来传回回调函数(callback)。 不允许直接等待一个 promise 执行完毕是为了鼓励用户书写非阻塞的代码,不然用户会更乐意写阻塞的代码,因为它比 promise 和回调函数更简单。

然而,为了同步 promise, 我们需要允许 promise 之间相互等待。换句话说,如果一个异步的操作(例如封装在一个 promise 中)就应该去等待另一个异步的操作去完成。但是 JavaScript 解释器如何判断一个操作是否在 promise 中运行呢?

答案就是 async 关键字。每一个 async 函数都会返回一个 promise。也就是说, JavaScript 解释器就会把所有在 aysnc 函数中的操作封装到 promise 中并异步运行。这样就可以让它们去等待其他的 promise 完成。

按下 await 关键字,await 只能在 async 函数中使用,作用是让我们同步的等待另一个 promise 执行完毕。如果在 async 函数之外使用 promise 的话,依旧需要使用 then 回调函数:

async function f() {// 返回值将作为 promise 被处理(resolve)之后的结果const response = await rp('http://example.com/');console.log(response);
}
// 不能在 async 函数之外使用 await 关键字
// 需要使用 then 回调
f().then(() => console.log('Finished'));

现在,来看看如何解决刚在在上面一节出现的问题:

// 将解决问题的方法封装到一个异步的函数中
async function solution() {// 等待第一个 HTTP 调用并且打印出结果console.log(await rp('http://example.com/'));// 生成 HTTP 调用但是不等待它们执行完毕 - 同时运行const call2Promise = rp('http://example.com/'); // 不等待! // Line 7const call3Promise = rp('http://example.com/'); // 不等待! // Line 8//在它们都被调用之后 - 等待它们执行完毕const response2 = await call2Promise; // Line 11const response3 = await call3Promise; // Line 12console.log(response2);console.log(response3);
}// 调用 async 函数
solution().then(() => console.log('Finished'))

在上面的代码段中,我们将解决方案封装到了一个 async 函数中,这样我们就可以直接的等待(await) promise 执行完毕。这样避免了使用 then 回调函数。 最后,我们调用了 async 函数,这个函数只是简单的生成了一个封装调用其他 promisepromise

在第一个例子(没有 asyncawait)中,那些 promise 会并行启动。这种情况下我们进行了同样的操作(第7,8行)。注意,直到11到12行,我们都没有使用 await。当所有的promise都执行完毕(resolve),我们才去阻塞程序的执行。之后,我们知道两个 promise 都执行完毕了(就像在之前的例子中,使用 Promise.all(...).then(...) 一样)。

在底层的计算过程上,这个过程和先前章节所述的过程是相同的,但是代码更加直观,可读性更好。

在引擎中,async/await 实际上转成了 promise 和 then传入的回调函数。换句话说,它是 promise 的语法糖。每次我们使用 await,解释器就会生成一个 promise,然后把其余的操作从 async 函数取出来放到 then 传入的回调函数中。

考虑一下下面的例子:

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

函数f在底层计算过程描述如下图。由于f是异步的,它将和调用者同步运行:

函数f开始运行并且生成了一个promise。同时,函数其余的部分被封装到回调函数中,安排在promise执行完毕之后再执行。

错误处理

在前面几个例子中,我们假设promise 成功的解决(reslove).于是,等待一个promise返回结果。事实上,如果等待的promise失败(reject)了,那么async函数将会返回一个异常(exception)。我们可以使用标准的try/catch去处理这种情况:

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

如果一个async函数没有处理异常,不管它是一个被拒绝(reject)的promise还是其他的 bug 造成的,它将返回一个被拒绝(reject)的promise:

async function f() {// 抛出异常const promiseResult = await Promise.reject('Error');
}// 将打印 "Error"
f().then(() => console.log('Success')).catch(err => console.log(err))async function g() {throw "Error";
}// 将打印 “Error”
g().then(() => console.log('Success')).catch(err => console.log(err))

使用已知的异常处理机制将使我们方便的处理被拒绝(reject)的promise

讨论

Async/awaitpromises的一种补充语言结构。它允许我们使用较少样板的 promise。然而async/await不能取代纯粹 promise 的需要。例如,如果从一个普通的函数或者全局范围内调用一个async函数,我们无法使用await,我们将借助于普通的promises(译者注:原文使用的是vanilla promise):

async function fAsync() {// 事实上返回值是 Promise.resolve(5)return 5;
}
// 不能调用 await fAsync(), 需要使用 then/catch
fAsync().then(r => console.log(`result is ${r}`));

我通常尝试将我大部分的异步逻辑封装到一个或者几个 async 函数中,然后从非异步的代码中调用。这极大地减少了我编写then/catch回调的数量。

async/await 结构是更简洁处理 promise 的语法糖。每一个 async/await 结构都可以使用纯粹的 promise 重写。最终,这是一个风格和简洁方面的问题。

学者们指出并发( concurrency )和并行( parallelism )有区别。查看Rob Pike关于该主题或我之前的帖子。并发是关于组合独立进程(在过程的一般含义中)一起工作,而并行是关于实际上同时执行多个进程。并发是关于应用程序的设计和结构,而并行性就是实际的执行。

以多线程应用程序为例。将应用程序分隔为线程定义其并发模型。这些线程在可用内核上的映射定义了其级别或并行。并发系统可以在单个处理器上有效运行,在这种情况下,它不是并行的。

在这种情况下,promise允许我们将程序分解为可并行运行的并发模块。实际的 JavaScript执行是否并行取决于 JavaScript 解释器实现。例如,Node.js是单线程的,如果 promise 是 CPU 绑定的,那么并不会看到很多并行进程。然而,如果您通过类似 Nashorn 的工具将代码编译成 java 字节码,理论上你可以在不同的 CPU 核心上映射CPU绑定的 promise 并实现并行运行。因此,在我看来,promise(普通或通过async/await)构成了JavaScript应用程序的并发模型。

文章转自 | Github
文章链接 | 图解 Await 和 Async

@async 如何返回list_图解 Await 和 Async相关推荐

  1. async js 返回值_JS异步编程 | Async / Await / Generator 实现原理解析

    async/await实现 在多个回调依赖的场景中,尽管Promise通过链式调用取代了回调嵌套,但过多的链式调用可读性仍然不佳,流程控制也不方便,ES7 提出的async 函数,终于让 JS 对于异 ...

  2. async js 返回值_async函数的返回值

    async函数其实是Geneator函数的语法糖. 1.async函数的返回值是Promise对象,可以用then方法指定下一步的操作.async函数可以看做多个异步操作,包装成一个Promise对象 ...

  3. [转]小心C# 5.0 中的await and async模式造成的死锁

    原文链接 https://www.cnblogs.com/OpenCoder/p/4434574.html 内容 UI Example Consider the example below. A bu ...

  4. .Net 多线程 异步编程 Await、Async和Task

    await和async简介   await和async是在C#5中引入,并且在.NetFramewor4.5以及.NetCore中进行了支持.主要是解决性能瓶颈,并且增强系统的响应能力. msdn关于 ...

  5. C#~异步编程再续~await与async引起的w3wp.exe崩溃-问题友好的解决

    返回目录 关于死锁的原因 理解该死锁的原因在于理解await 处理contexts的方式,默认的,当一个未完成的Task 被await的时候,当前的上下文将在该Task完成的时候重新获得并继续执行剩余 ...

  6. 使用await和async关键字开发nodejs应用批量取出简书网站的文章标题和超链接

    ES6的await和async出来已经那么久了,一起来学习一下吧. 还是用我之前的读取某个简书用户所有文章列表的例子. 先看主函数框架: async function downloadArticle( ...

  7. C#语法——await与async的正确打开方式

    C#5.0推出了新语法,await与async,但相信大家还是很少使用它们.关于await与async有很多文章讲解,但有没有这样一种感觉,你看完后,总感觉这东西很不错,但用的时候,总是想不起来,或者 ...

  8. async js 返回值_获取JavaScript异步函数的返回值

    今天研究一个小问题: 怎么拿到JavaScript异步函数的返回值? 1.错误尝试 当年未入行时,我的最初尝试: function getSomething() { var r = 0; setTim ...

  9. C#中的异步编程--探索await与async关键字的奥妙之处,原来理解和使用异步编程可以这么简单

    前言 await与async是C#5.0推出的新语法,关于await与async有很多文章讲解.但看完后有没有这样一种感觉,感觉这东西像是不错,但好像就是看不太懂,也不清楚该怎么使用.虽然偶有接触,但 ...

最新文章

  1. JavaScript调用Applet的函数
  2. JAVA移慎_谨慎使用Java8的默认方法
  3. mac 查看mysql是否安装_[简明核心系列] 三分钟Mac安装MySQL教程
  4. GitHub 给安全行业的四大启示
  5. 公共技术点之 Java 反射 Reflection
  6. ubuntu14.04下通过.frm, .MYD,.MYI文件恢复建立mysql数据库
  7. Xamarin.Android SharedPreferences的使用方法
  8. 形容PHP程序员的语句,形容程序员的句子
  9. 03.JavaScript对DOM操作
  10. C语言 | 结构体指针
  11. 安装好MongoDB,但服务中没有MongoDB服务的解决办法
  12. 4-Ubuntu—终端下重启与关机
  13. 【转】frame与bounds的区别比较
  14. TOPSIS优劣解距离法
  15. c语言输入相应的成绩评定信息,C语言上机练习题记答案.doc
  16. Java文件操作——简单文件搜索优化版本Lambda优化
  17. Linux shell爬虫实现树洞网鼓励师(自动回复Robot)
  18. 电脑文件剪切到U盘,为什么不见了?这4个技巧帮你找回丢失文件
  19. 冯诺伊曼出生日期星期几_天才冯·诺依曼与冯·诺依曼瓶颈
  20. 关于小程序的bindscrolltolower事件失效,已解决

热门文章

  1. 2进制、8进制、10进制、16进制...各种进制间的轻松转换(c#)
  2. 开发手记之实现web.config的快速配置(转载)
  3. 16进制 ksh_AIX系统中如何统计进程打开的文件数目
  4. 怎么把pdf转换为html,如何将PDF转换成HTML网页格式呢?
  5. [转载] 【Java】将一个字符串的字符排序,按ASCII表的顺序从小到大
  6. kotlin 查找id_Kotlin程序查找立方体区域
  7. Java StreamTokenizer nextToken()方法与示例
  8. mysql语句数据库_数据库的Mysql语句
  9. lvs负载均衡—NAT模式
  10. fiddler修改支付金额_不容忽视的记账工具:支付宝记账