这篇文章是异步发展流程系列的最后一篇,可能会涉及 Promise、Generators、co 等前置知识,若是对这些不是很了解能够看这个系列的前三篇:编程
若是已经具有这些前置知识,那咱们继续看看今天的主角,JavaScript 异步编程的终极大招 async/await
。数组
async/await
指的是两个关键字,是 ES7 引入的新标准,async
关键字用于声明 async
函数,await
关键字用来等待异步(必须是 Promise)操做,说白了 async/await
就是 Generators + co 的语法糖。promise
async/await
和 Generators + co 的写法很是的类似,只是把用于声明 Generator 函数的 *
关键字替换成了 async
并写在了 function
关键字的前面,把 yield
关键字替换成了 await
;另外,async
函数是基于 Promise 的,await
关键字后面等待的异步操做必须是一个 Promise 实例,固然也能够是原始类型的值,只不过这时的执行效果等同于同步,与 Generator 不一样的是,await
关键字前可使用变量去接收这个正在等待的 Promise 实例执行后的结果。浏览器
async
函数返回一个 Promise 实例,可使用 then
方法添加回调函数。当函数执行的时候,只要遇到 await
就会等待,直到 await
后面的同步或异步操做完成,再接着执行函数体内后面的语句。并发
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
// 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 的语法糖。异步编程
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))); // error
在 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();
使用 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]); // 正常执行
<hr/>
async/await
的实现原理,其实就是在 async
函数内部逻辑映射成了 Generator 函数并集成了一个相似于 co
的执行器,因此咱们使用 async/await
的时候,代码更简洁,没有了本身触发遍历器的 next
或调用 co
充当执行器的过程,只须要关心 async
函数的内部逻辑就能够了,由于写法与同步相同,更提升了代码的可读性,因此说 async/await
是异步编程的终极大招。
因为 async/await
是 ES7 规范,在浏览器端的支持并非那么的友好,因此如今这种写法多用在 NodeJS 的异步操做当中,在 NodeJS 框架 Koa 2.x
版本获得普遍应用。
最后但愿你们在读过异步发展流程这个系列以后,对 JavaScript 异步已经有了较深的认识,并能够在不一样状况下游刃有余的使用这些处理异步的编程手段。