摘要:简介指的是两个关键字,是引入的新标准,关键字用于声明函数,关键字用来等待异步必须是操作,说白了就是的语法糖。最后希望大家在读过异步发展流程这个系列之后,对异步已经有了较深的认识,并可以在不同情况下游刃有余的使用这些处理异步的编程手段。
阅读原文
这篇文章是异步发展流程系列的最后一篇,可能会涉及 Promise、Generators、co 等前置知识,如果对这些不是很了解可以看这个系列的前三篇:
异步发展流程 —— Promise 的基本使用
异步发展流程 —— 手写一个符合 Promise/A+ 规范的 Promise
异步发展流程 —— Generators + co 让异步更优雅
如果已经具备这些前置知识,那我们继续看看今天的主角,JavaScript 异步编程的终极大招 async/await。
async/await 简介async/await 指的是两个关键字,是 ES7 引入的新标准,async 关键字用于声明 async 函数,await 关键字用来等待异步(必须是 Promise)操作,说白了 async/await 就是 Generators + co 的语法糖。
async/await 和 Generators + co 的写法非常的相似,只是把用于声明 Generator 函数的 * 关键字替换成了 async 并写在了 function 关键字的前面,把 yield 关键字替换成了 await;另外,async 函数是基于 Promise 的,await 关键字后面等待的异步操作必须是一个 Promise 实例,当然也可以是原始类型的值,只不过这时的执行效果等同于同步,与 Generator 不同的是,await 关键字前可以使用变量去接收这个正在等待的 Promise 实例执行后的结果。
async 函数的基本用法async 函数返回一个 Promise 实例,可以使用 then 方法添加回调函数。当函数执行的时候,只要遇到 await 就会等待,直到 await 后面的同步或异步操作完成,再接着执行函数体内后面的语句。
1、async 函数声明async 的声明方式大概有以下几种:
// async 函数声明 // 函数声明 async function fn() {} // 函数表达式 const fn = async function() {}; // 箭头函数 const fn = async () => {}; // 作为对象的方法 let obj = { async fn() {} }; // 作为 class 的方法 class Person(name) { constructor () { this.name = name; } async getName() { const name = await this.name; return name; } }
在上一篇介绍 Generators + co 的文章中我们举了一个例子,使用 NodeJS 的 fs 模块连续异步读文件,第一个文件名为 a.txt,读到的内容为 b.txt,作为要读的第二个文件的文件名,继续读 b.txt 后将读到的内容 “Hello world” 打印出来。
我们来使用 async/await 的方式来实现一下:
// async 函数实现文件读取 // 引入依赖 const fs = require("fs"); const util = require("util"); // 将 fs.readFile 转换成 Promise const readFile = util.promisify(fs.readFile); // 声明 async 函数 async function read(file) { let aData = await readFile(file, "utf8"); let bData = await readFile(aData, "utf8"); return bData; } // 调用 async 函数 read("a.txt").then(data => { console.log(data); // Hello world });
其实对比上一篇文章 Generator 的案例,与 Generator 函数一样,写法像同步,执行是异步,不同的是我们即没有手动调用 next 方法,也没有借助 co 库,其实是 async 函数内部集成了类似于 co 的执行器,帮我们在异步完成后自动向下执行代码,所以说 async/await 是 Generators + co 的语法糖。
2、async 函数错误处理async 函数内部如果执行错误可以有三种方式进行错误处理:
在 await 后面的 Promise 实例使用 then 方法错误的回调或 catch 方法进行错误处理;
如果有多个 await,可以在 async 函数执行完后使用 catch 方法统一处理;
由于 async 内部代码是同步的写法,多个 await 的情况也可以使用 try...catch... 进行处理。
需要注意的是,如果在 async 函数内部使用了 try...catch... 又在函数执行完后使用了 catch,错误会优先被同步的 try...catch... 捕获到,后面的 catch 就不会再捕获了。
// async 函数异常捕获 // 第一种 async function fn() { let result = await Promise.reject("error").catch(err => { console.log(err); }); } fn(); // error // 第二种 async function fn() { try { let val1 = await Promise.reject("error"); let val2 = await Promise.resolve("success"); } catch (e) { console.log(e); } } fn(); // error // 第三种 async function fn() { let val1 = await Promise.resolve("success"); let val2 = await Promise.reject("error"); } fn().catch((err => console.log(err))); // error3、await 异步并发
在 async 函数中,如果有多个 await 互不依赖,这种情况下如果执行一个,等待一个完成,再执行一个,再等待完成,这样是很浪费性能的,所以我们要把这些异步操作同时触发。
假设我们异步读取两个文件,且这两个文件不相关,我可以使用下面的方式来实现:
// await 异步并发 // 前置 const fs = require("fs"); const util = require("util"); const readFile = util.promisify(fs.readFile); // 需要改进的 async 函数 async function fn() { let aData = await readFile("a.txt", "utf8"); let bData = await readFile("b.txt", "utf8"); return [aData, bData]; } fn(); // 在 async 函数外部触发异步 let aDataPromise = readFile("a.txt", "utf8"); let bDataPromise = readFile("b.txt", "utf8"); async function fn() { let aData = await aDataPromise; let bData = await bDataPromise; return [aData, bData]; } fn(); // 使用 Promise.all async function fn() { let dataArr = await Promise.all( readFile("a.txt", "utf8"), readFile("a.txt", "utf8") ); return dataArr; } fn();4、使用 async/await 的注意点
使用 async/await 应注意以下几点:
对 await 习惯性错误处理;
await 命令后互不依赖的异步应同时触发;
async 函数中,函数的执行上/下文发生变化时,不能使用 await(如使用 forEach 循环的回调中)。
针对第一点,在 async 函数中 await 命令后面大多情况下是 Promise 异步操作,运行结果可能出现错误并调用 reject 函数,最好对这个 await 语句进行错误处理,具体方式参照 async 函数基本用法中关于错误处理的内容。
针对第二点,如果两个或多个 await 命令后的异步操作没有依赖关系,执行时,需先触发第一个,等待异步完成,再触发第二个,再等异步完成,依次类推,这样比较耗时,性能不好,所以应该将这些异步操作同时触发,触发方式参照 async 函数基本用法中的 await 异步并发的内容。
针对第三点,如果声明一个 async 函数并传入一个数组,数组里面存储的都是 Promise 实例,若使用 forEach 循环数组,由于函数的执行上/下文发生了变化,此时使用 await 命令会报错。
// 循环内使用 await // 创建 Promise 实例 let p1 = Promise.resolve("p1 success"); let p2 = Promise.resolve("p2 success"); let p3 = Promise.resolve("p3 success"); // async 函数 async function fn(promises) { promise.forEach(function (promise) { await promise; }); } fn([p1, p2, p3]); // 执行时报错 // 修改方式 async function fn(promises) { for(let i = 0; i < promises.length; i++) { await pormises[i]; } } fn([p1, p2, p3]); // 正常执行
async/await 的实现原理,其实就是在 async 函数内部逻辑映射成了 Generator 函数并集成了一个类似于 co 的执行器,所以我们使用 async/await 的时候,代码更简洁,没有了自己触发遍历器的 next 或调用 co 充当执行器的过程,只需要关心 async 函数的内部逻辑就可以了,因为写法与同步相同,更提高了代码的可读性,所以说 async/await 是异步编程的终极大招。
由于 async/await 是 ES7 规范,在浏览器端的支持并不是那么的友好,所以现在这种写法多用在 NodeJS 的异步操作当中,在 NodeJS 框架 Koa 2.x 版本得到广泛应用。
最后希望大家在读过异步发展流程这个系列之后,对 JavaScript 异步已经有了较深的认识,并可以在不同情况下游刃有余的使用这些处理异步的编程手段。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/98279.html
摘要:所以异步编程对语言太重要。异步编程我们就以用户注册这个特别常见的场景为例,讲讲异步编程。这种层层嵌套被称为回调地狱。相比回调函数而言,代码可读性更高,代码的执行顺序一目了然。函数内部语句返回的值,会成为方法回调函数的参数。 单线程是Javascript语言最本质的特性之一,Javascript引擎在运行js代码的时候,同一个时间只能执行单个任务。 这种模式的好处是实现起来比较简单,执行...
摘要:异步流程管理说白了就是为了解决回调地狱的问题。对象代表一个异步操作,有三种状态进行中已成功和已失败。如果改变已经发生了,你再对对象添加回调函数,也会立即得到这个结果。执行函数后返回的是一个遍历器对象,可以依次遍历函数内部的每一个状态。 javascript -- 深度解析异步解决方案 高级语言层出不穷, 然而唯 js 鹤立鸡群, 这要说道js的设计理念, js天生为异步而生, 正如布道...
摘要:的翻译文档由的维护很多人说,阮老师已经有一本关于的书了入门,觉得看看这本书就足够了。前端的异步解决方案之和异步编程模式在前端开发过程中,显得越来越重要。为了让编程更美好,我们就需要引入来降低异步编程的复杂性。 JavaScript Promise 迷你书(中文版) 超详细介绍promise的gitbook,看完再不会promise...... 本书的目的是以目前还在制定中的ECMASc...
摘要:感谢大神的免费的计算机编程类中文书籍收录并推荐地址,以后在仓库里更新地址,声音版全文狼叔如何正确的学习简介现在,越来越多的科技公司和开发者开始使用开发各种应用。 说明 2017-12-14 我发了一篇文章《没用过Node.js,就别瞎逼逼》是因为有人在知乎上黑Node.js。那篇文章的反响还是相当不错的,甚至连著名的hax贺老都很认同,下班时读那篇文章,竟然坐车的还坐过站了。大家可以很...
摘要:感谢大神的免费的计算机编程类中文书籍收录并推荐地址,以后在仓库里更新地址,声音版全文狼叔如何正确的学习简介现在,越来越多的科技公司和开发者开始使用开发各种应用。 说明 2017-12-14 我发了一篇文章《没用过Node.js,就别瞎逼逼》是因为有人在知乎上黑Node.js。那篇文章的反响还是相当不错的,甚至连著名的hax贺老都很认同,下班时读那篇文章,竟然坐车的还坐过站了。大家可以很...
阅读 2721·2021-11-22 13:54
阅读 1062·2021-10-14 09:48
阅读 2291·2021-09-08 09:35
阅读 1549·2019-08-30 15:53
阅读 1166·2019-08-30 13:14
阅读 605·2019-08-30 13:09
阅读 2520·2019-08-30 10:57
阅读 3333·2019-08-29 13:18