Promise 在 JavaScript 上发布之初就在互联网上流行了起来 — 它们帮开发人员摆脱了回调地狱,解决了在很多地方困扰 JavaScript 开发者的异步问题。但 Promises 也远非完美。它们一直请求回调,在一些复杂的问题上仍会有些杂乱和一些难以置信的冗余。

随着 ES6 的到来(现在被称作 ES2015),除了引入 Promise 的规范,不需要请求那些数不尽的库之外,我们还有了生成器。生成器可在函数内部停止执行,这意味着可把它们封装在一个多用途的函数中,我们可在代码移动到下一行之前等待异步操作完成。突然你的异步代码可能就开始看起来同步了。

这只是第一步。异步函数因今年加入 ES2017,已进行标准化,本地支持也进一步优化。异步函数的理念是使用生成器进行异步编程,并给出他们自己的语义和语法。因此,你无须使用库来获取封装的实用函数,因为这些都会在后台处理。

运行文章中的 async/await 实例,你需要一个能兼容的浏览器。

运行兼容

在客户端,Chrome、Firefox 和 Opera 能很好地支持异步函数。

从 7.6 版本开始,Node.js 默认启用 async/await。

异步函数和生成器对比

这有个使用生成器进行异步编程的实例,用的是 Q 库:

  1. var doAsyncOp = Q.async(function* () {
  2. var val = yield asynchronousOperation();
  3. console.log(val);
  4. return val;
  5. });

Q.async 是个封装函数,处理场景后的事情。其中 * 表示作为一个生成器函数的功能,yield 表示停止函数,并用封装函数代替。Q.async 将会返回一个函数,你可对它赋值,就像赋值 doAsyncOp 一样,随后再调用。

ES7 中的新语法更简洁,操作示例如下:

  1. async function doAsyncOp () {
  2. var val = await asynchronousOperation();
  3. console.log(val);
  4. return val;
  5. };

差异不大,我们删除了一个封装的函数和 * 符号,转而用 async 关键字代替。yield 关键字也被 await 取代。这两个例子事实上做的事是相同的:在 asynchronousOperation 完成之后,赋值给 val,然后进行输出并返回结果。

将 Promises 转换成异步函数

如果我们使用 Vanilla Promises 的话前面的示例将会是什么样?

  1. function doAsyncOp () {
  2. return asynchronousOperation().then(function(val) {
  3. console.log(val);
  4. return val;
  5. });
  6. };

这里有相同的代码行数,但这是因为 then 和给它传递的回调函数增加了很多的额外代码。另一个让人厌烦的是两个 return 关键字。这一直有些事困扰着我,因为它很难弄清楚使用 promises 的函数确切的返回是什么。

就像你看到的,这个函数返回一个 promises,将会赋值给 val,猜一下生成器和异步函数示例做了什么!无论你在这个函数返回了什么,你其实是暗地里返回一个 promise 解析到那个值。如果你根本就没有返回任何值,你暗地里返回的 promise 解析为 undefined。

链式操作

Promise 之所以能受到众人追捧,其中一个方面是因为它能以链式调用的方式把多个异步操作连接起来,避免了嵌入形式的回调。不过 async 函数在这个方面甚至比 Promise 做得还好。

下面演示了如何使用 Promise 来进行链式操作(我们只是简单的多次运行 asynchronousOperation 来进行演示)。

  1. function doAsyncOp() {
  2. return asynchronousOperation()
  3. .then(function(val) {
  4. return asynchronousOperation(val);
  5. })
  6. .then(function(val) {
  7. return asynchronousOperation(val);
  8. })
  9. .then(function(val) {
  10. return asynchronousOperation(val);
  11. });
  12. }

使用 async 函数,只需要像编写同步代码那样调用 asynchronousOperation:

  1. async function doAsyncOp () {
  2. var val = await asynchronousOperation();
  3. val = await asynchronousOperation(val);
  4. val = await asynchronousOperation(val);
  5. return await asynchronousOperation(val);
  6. };

甚至最后的 return 语句中都不需要使用 await,因为用或不用,它都返回了包含了可处理终值的 Promise。

并发操作

Promise 还有另一个伟大的特性,它们可以同时进行多个异步操作,等他们全部完成之后再继续进行其它事件。ES2015 规范中提供了 Promise.all(),就是用来干这个事情的。

这里有一个示例:

  1. function doAsyncOp() {
  2. return Promise.all([
  3. asynchronousOperation(),
  4. asynchronousOperation()
  5. ]).then(function(vals) {
  6. vals.forEach(console.log);
  7. return vals;
  8. });
  9. }

Promise.all() 也可以当作 async 函数使用:

  1. async function doAsyncOp() {
  2. var vals = await Promise.all([
  3. asynchronousOperation(),
  4. asynchronousOperation()
  5. ]);
  6. vals.forEach(console.log.bind(console));
  7. return vals;
  8. }

这里就算使用了 Promise.all,代码仍然很清楚。

处理拒绝

Promises 可以被接受(resovled)也可以被拒绝(rejected)。被拒绝的 Promise 可以通过一个函数来处理,这个处理函数要传递给 then,作为其第二个参数,或者传递给 catch 方法。现在我们没有使用 Promise API 中的方法,应该怎么处理拒绝?可以通过 try 和 catch 来处理。使用 async 函数的时候,拒绝被当作错误来传递,这样它们就可以通过 JavaScript 本身支持的错误处理代码来处理。

  1. function doAsyncOp() {
  2. return asynchronousOperation()
  3. .then(function(val) {
  4. return asynchronousOperation(val);
  5. })
  6. .then(function(val) {
  7. return asynchronousOperation(val);
  8. })
  9. .catch(function(err) {
  10. console.error(err);
  11. });
  12. }

这与我们链式处理的示例非常相似,只是把它的最后一环改成了调用 catch。如果用 async 函数来写,会像下面这样。

  1. async function doAsyncOp () {
  2. try {
  3. var val = await asynchronousOperation();
  4. val = await asynchronousOperation(val);
  5. return await asynchronousOperation(val);
  6. } catch (err) {
  7. console.err(err);
  8. }
  9. };

它不像其它往 async 函数的转换那样简洁,但是确实跟写同步代码一样。如果你在这里不捕捉错误,它会延着调用链一直向上抛出,直到在某处被捕捉处理。如果它一直未被捕捉,它最终会中止程序并抛出一个运行时错误。Promise 以同样的方式运作,只是拒绝不必当作错误来处理;它们可能只是一个说明错误情况的字符串。如果你不捕捉被创建为错误的拒绝,你会看到一个运行时错误,不过如果你只是使用一个字符串,会失败却不会有输出。

中断 Promise

拒绝原生的 Promise,只需要使用 Promise 构建函数中的 reject 就好,当然也可以直接抛出错误——在 Promise 的构造函数中,在 then 或 catch 的回调中抛出都可以。如果是在其它地方抛出错误,Promise 就管不了了。

这里有一些拒绝 Promise 的示例:

  1. function doAsyncOp() {
  2. return new Promise(function(resolve, reject) {
  3. if (somethingIsBad) {
  4. reject("something is bad");
  5. }
  6. resolve("nothing is bad");
  7. });
  8. }
  9. /*-- or --*/
  10. function doAsyncOp() {
  11. return new Promise(function(resolve, reject) {
  12. if (somethingIsBad) {
  13. reject(new Error("something is bad"));
  14. }
  15. resolve("nothing is bad");
  16. });
  17. }
  18. /*-- or --*/
  19. function doAsyncOp() {
  20. return new Promise(function(resolve, reject) {
  21. if (somethingIsBad) {
  22. throw new Error("something is bad");
  23. }
  24. resolve("nothing is bad");
  25. });
  26. }

一般来说,最好使用 new Error,因为它会包含错误相关的其它信息,比如抛出位置的行号,以及可能会有用的调用栈。

这里有一些抛出 Promise 不能捕捉的错误的示例:

  1. function doAsyncOp() {
  2. // the next line will kill execution
  3. throw new Error("something is bad");
  4. return new Promise(function(resolve, reject) {
  5. if (somethingIsBad) {
  6. throw new Error("something is bad");
  7. }
  8. resolve("nothing is bad");
  9. });
  10. }
  11. // assume `doAsyncOp` does not have the killing error
  12. function x() {
  13. var val = doAsyncOp().then(function() {
  14. // this one will work just fine
  15. throw new Error("I just think an error should be here");
  16. });
  17. // this one will kill execution
  18. throw new Error("The more errors, the merrier");
  19. return val;
  20. }

在 async 函数的 Promise 中抛出错误就不会产生有关范围的问题——你可以在 async 函数中随时随地抛出错误,它总会被 Promise 抓住:

  1. async function doAsyncOp() {
  2. // the next line is fine
  3. throw new Error("something is bad");
  4. if (somethingIsBad) {
  5. // this one is good too
  6. throw new Error("something is bad");
  7. }
  8. return "nothing is bad";
  9. }
  10. // assume `doAsyncOp` does not have the killing error
  11. async function x() {
  12. var val = await doAsyncOp();
  13. // this one will work just fine
  14. throw new Error("I just think an error should be here");
  15. return val;
  16. }

当然,我们永远不会运行到 doAsyncOp 中的第二个错误,也不会运行到 return 语句,因为在那之前抛出的错误已经中止了函数运行。

问题

如果你刚开始使用 async 函数,需要小心嵌套函数的问题。比如,如果你的 async 函数中有另一个函数(通常是回调),你可能认为可以在其中使用 await ,但实际不能。你只能直接在 async 函数中使用 await 。

比如,这段代码无法运行:

  1. async function getAllFiles(fileNames) {
  2. return Promise.all(
  3. fileNames.map(function(fileName) {
  4. var file = await getFileAsync(fileName);
  5. return parse(file);
  6. })
  7. );
  8. }

第 4 行的 await 无效,因为它是在一个普通函数中使用的。不过可以通过为回调函数添加 async 关键字来解决这个问题。

  1. async function getAllFiles(fileNames) {
  2. return Promise.all(
  3. fileNames.map(async function(fileName) {
  4. var file = await getFileAsync(fileName);
  5. return parse(file);
  6. })
  7. );
  8. }

你看到它的时候会觉得理所当然,即便如此,仍然需要小心这种情况。

也许你还想知道等价的使用 Promise 的代码:

  1. function getAllFiles(fileNames) {
  2. return Promise.all(
  3. fileNames.map(function(fileName) {
  4. return getFileAsync(fileName).then(function(file) {
  5. return parse(file);
  6. });
  7. })
  8. );
  9. }

接下来的问题是关于把 async 函数看作同步函数。需要记住的是,async 函数内部的的代码是同步运行的,但是它会立即返回一个 Promise,并继续运行外面的代码,比如:

  1. var a = doAsyncOp(); // one of the working ones from earlier
  2. console.log(a);
  3. a.then(function() {
  4. console.log("`a` finished");
  5. });
  6. console.log("hello");
  7. /* -- will output -- */
  8. Promise Object
  9. hello
  10. `a` finished

你会看到 async 函数实际使用了内置的 Promise。这让我们思考 async 函数中的同步行为,其它人可以通过普通的 Promise API 调用我们的 async 函数,也可以使用它们自己的 async 函数来调用。

如今,更好的异步代码!

即使你本身不能使用异步代码,你也可以进行编写或使用工具将其编译为 ES5。 异步函数能让代码更易于阅读,更易于维护。 只要我们有 source maps,我们可以随时使用更干净的 ES2017 代码。

有许多可以将异步功能(和其他 ES2015+功能)编译成 ES5 代码的工具。 如果您使用的是 Babel,这只是安装 ES2017 preset 的例子。

作者:佚名

来源:51CTO

用Async函数简化异步代码相关推荐

  1. 用 Async 函数简化异步代码

    Promise 在 JavaScript 上发布之初就在互联网上流行了起来 - 它们帮开发人员摆脱了回调地狱,解决了在很多地方困扰 JavaScript 开发者的异步问题.但 Promises 也远非 ...

  2. es6 async函数的异步迭代器

    async函数的异步迭代器 <迭代器>一章说过,Iterator 接口是一种数据遍历的协议,只要调用迭代器对象的next方法,就会得到一个对象,表示当前遍历指针所在的那个位置的信息.nex ...

  3. python输出一个简单的田字格、用函数简化其代码_Solution Method: 洛谷 P1001 A+B Problem(Python 3 基本输入输出方法及代码简化)...

    本文从 洛谷 P1001 A+B Problem 为例,讲一讲 Python 3 在算法竞赛中的一些基本输入输出方法,以及一些利用 Python 3 特性的代码简化 以下为本文将涉及的内容: inpu ...

  4. 使用Async对Angular异步代码进行单元测试

    https://www.digitalocean.com/community/tutorials/angular-testing-async-fakeasync The async utility t ...

  5. 程序练习题3.5输出了一个简单的田字格,用函数简化其代码,输出更大的田字格。

    #e5.1函数化输出田字格 def tianzige(h,l):a,b,c,d = "+","-","丨"," "han ...

  6. python输出一个简单的田字格、用函数简化其代码_Python练习100例,随时随地学习,菜鸟用了都说好...

    学习Python最害怕的是枯燥无味的,一定要注重Python实战! 新手用这100个Python实例测试下你的学习水平再好不过,Python前期的学习,尽量多练习,光学不练效率会很低,多练习其实也是给 ...

  7. 【JS】930- 更快的 async 函数和 promises

    作者:语雀 链接:https://www.yuque.com/es2049/blog/yfqmu1 原文链接:https://v8.dev/blog/fast-async JavaScript 的异步 ...

  8. 「译」更快的 async 函数和 promises

    来源:https://www.yuque.com/es2049/blog 译自:Faster async functions and promises JavaScript 的异步过程一直被认为是不够 ...

  9. 避免回调地狱的解决方案 async/await:用同步的方式去写异步代码

    文章目录 前言 一.引入异步编程 二.常见处理异步编程的几种方式 1.Generator函数 2.Promise函数 3.async/await 总结 前言 这篇文章主要给大家分享一下,自己关于异步编 ...

最新文章

  1. 2022-2028年中国无滴消雾大棚膜行业市场研究及前瞻分析报告
  2. python源程序执行的方式是什么执行-python调用可执行文件的方法
  3. C语言指针数组和数组指针
  4. hdfs yarn hbase pid文件被删除解决办法:修改hadoop-daemon.sh yarn-daemon.sh hbase-daemon.sh中PID_DIR存储路径
  5. 鸿蒙上海开发者日直播,华为鸿蒙 OS 开发者日于 4月17 日上海举行
  6. 计算机英语讲课笔记07
  7. php select事件模型,select:联动+change事件(数据从后台获取)总结
  8. labview混合编程学习
  9. 应用密码学:单表代替密码简单介绍
  10. 如何使用dd工具进行磁盘读写性能测试
  11. sb 讲解 (!(~+[])+{})[--[~+][+[]]*[~+[]] + ~~!+[]]+({}+[])[[~!+[]]*~+[]]
  12. pytorch安装(离线包)
  13. java分流什么意思_Flink如何分流数据
  14. 计算机二级office的考试内容,计算机二级office考试内容有啥
  15. 年货来咯:精选年度最受欢迎干货,覆盖客户端、服务端、前端、数据、算法……...
  16. 高等数学(第七版)同济大学 习题12-8 个人解答
  17. 如何利用蜂鸣器制作MIDI音乐
  18. new创建类对象与直接定义的区别
  19. DM8达梦数据库:系统中的错误码信息
  20. kafka官网示例说明--KafkaConsumer

热门文章

  1. 图解Transformer,读完这篇就够了
  2. 那些轻轻拍了拍Attention的后浪们
  3. 人工智能项目:需要注意的七件事
  4. 我人生的贵人系列之 - 文深刘
  5. 研究表明:无人驾驶技术减少拥堵加快进程
  6. 一段文字讲清楚Kubernetes的工作原理
  7. 人工智能和机器学习在治安管理方面意义重大
  8. 机器学习算法工程师的自我修养
  9. 干货丨谷歌最新机器学习术语表
  10. 干货 | 请收下这份2018学习清单:150个最好的机器学习,NLP和Python教程