今天, 带大家来谈谈 ES6 中的 async 函数, 我们在理解一个概念的时候, 无外乎这是三个方面
是什么
为什么
怎么用
如果感觉文章太长, 可以直接拉到下面, 看小结
sync 是什么
ES7 提供了 async 函数, 使得异步操作变得更加方便. async 函数是什么? 一句话, async 函数就是 Generator 函数的语法糖.
我们来个案例, 取读文件
Generator 函数
- var fs = require('fs');
- var readFile = function (fileName) {
- return new Promise(function (resolve, reject) {
- fs.readFile(fileName, function(error, data) {
- if (error) reject(error);
- resolve(data);
- });
- });
- };
- var gen = function* (){
- var f1 = yield readFile('/etc/fstab');
- var f2 = yield readFile('/etc/shells');
- console.log(f1.toString());
- console.log(f2.toString());
- };
写成 async 函数, 就是下面这样.
- var asyncReadFile = async function (){
- var f1 = await readFile('/etc/fstab');
- var f2 = await readFile('/etc/shells');
- console.log(f1.toString());
- console.log(f2.toString());
- };
一比较就会发现, async 函数就是将 Generator 函数的星号 (*) 替换成 async, 将 yield 替换成 await, 仅此而已.
我们会想, 为什么明明有 Generator 函数, 还需要 async 函数
为什么需要 async 函数
async 函数对 Generator 函数的改进, 体现在以下四点.
(1)内置执行器. Generator 函数的执行必须靠执行器, 所以才有了 co 模块, 而 async 函数自带执行器. 也就是说, async 函数的执行, 与普通函数一模一样, 只要一行.
var result = asyncReadFile();
上面的代码调用了 asyncReadFile 函数, 然后它就会自动执行, 输出最后结果. 这完全不像 Generator 函数, 需要调用 next 方法, 或者用 co 模块, 才能得到真正执行, 得到最后结果.
(2)更好的语义. async 和 await, 比起星号和 yield, 语义更清楚了. async 表示函数里有异步操作, await 表示紧跟在后面的表达式需要等待结果.
(3)更广的适用性. co 模块约定, yield 命令后面只能是 Thunk 函数或 Promise 对象, 而 async 函数的 await 命令后面, 可以是 Promise 对象和原始类型的值(数值, 字符串和布尔值, 但这时等同于同步操作).
(4)返回值是 Promise.async 函数的返回值是 Promise 对象, 这比 Generator 函数的返回值是 Iterator 对象方便多了. 你可以用 then 方法指定下一步的操作.
进一步说, async 函数完全可以看作多个异步操作, 包装成的一个 Promise 对象, 而 await 命令就是内部 then 命令的语法糖.
语法
async 怎么使用呢?
一个函数前面加上 async, 就可以让这个函数数成为异步函数, 跳出原本的执行顺序
- console.log(1)
- async function asyfun () {
- console.log(2)
- }
- asyfun();
- console.log(3)
- // 打印结果: 1,3,2
(1)async 函数返回一个 Promise 对象.
async 函数内部 return 语句返回的值, 会成为 then 方法回调函数的参数.
- async function f() {
- return 'hello world';
- }
- f().then(v => console.log(v))
- // "hello world"
上面代码中, 函数 f 内部 return 命令返回的值, 会被 then 方法回调函数接收到.
async 函数内部抛出错误, 会导致返回的 Promise 对象变为 reject 状态. 抛出的错误对象会被 catch 方法回调函数接收到.
- async function f() {
- throw new Error('出错了');
- }
- f().then(
- v => console.log(v),
- e => console.log(e)
- )
- // Error: 出错了
(2)async 函数返回的 Promise 对象, 必须等到内部所有 await 命令的 Promise 对象执行完, 才会发生状态改变. 也就是说, 只有 async 函数内部的异步操作执行完, 才会执行 then 方法指定的回调函数.
下面是一个例子.
- async function getTitle(url) {
- let response = await fetch(url);
- let html = await response.text();
- return html.match(/<title>([\s\S]+)<\/title>/i)[1];
- }
- getTitle('https://tc39.github.io/ecma262/').then(console.log)
- // "ECMAScript 2017 Language Specification"
(3)正常情况下, await 命令后面是一个 Promise 对象. 如果不是, 会被转成一个立即 resolve 的 Promise 对象.
- async function f() {
- return await 123;
- }
- f().then(v => console.log(v))
- // 123
上面代码中, await 命令的参数是数值 123, 它被转成 Promise 对象, 并立即 resolve.
await 命令后面的 Promise 对象如果变为 reject 状态, 则 reject 的参数会被 catch 方法的回调函数接收到.
- async function f() {
- await Promise.reject('出错了');
- }
- f()
- .then(v => console.log(v))
- .catch(e => console.log(e))
- // 出错了
注意, 上面代码中, await 语句前面没有 return, 但是 reject 方法的参数依然传入了 catch 方法的回调函数. 这里如果在 await 前面加上 return, 效果是一样的.
只要一个 await 语句后面的 Promise 变为 reject, 那么整个 async 函数都会中断执行.
- async function f() {
- await Promise.reject('出错了');
- await Promise.resolve('hello world'); // 不会执行
- }
上面代码中, 第二个 await 语句是不会执行的, 因为第一个 await 语句状态变成了 reject.
为了避免这个问题, 可以将第一个 await 放在 try...catch 结构里面, 这样第二个 await 就会执行.
- async function f() {
- try {
- await Promise.reject('出错了');
- } catch(e) {
- }
- return await Promise.resolve('hello world');
- }
- f()
- .then(v => console.log(v))
- // hello world
另一种方法是 await 后面的 Promise 对象再跟一个 catch 方面, 处理前面可能出现的错误.
- async function f() {
- await Promise.reject('出错了')
- .catch(e => console.log(e));
- return await Promise.resolve('hello world');
- }
- f()
- .then(v => console.log(v))
- // 出错了
- // hello world
如果有多个 await 命令, 可以统一放在 try...catch 结构中.
- async function main() {
- try {
- var val1 = await firstStep();
- var val2 = await secondStep(val1);
- var val3 = await thirdStep(val1, val2);
- console.log('Final:', val3);
- }
- catch (err) {
- console.error(err);
- }
- }
(4)如果 await 后面的异步操作出错, 那么等同于 async 函数返回的 Promise 对象被 reject.
- async function f() {
- await new Promise(function (resolve, reject) {
- throw new Error('出错了');
- });
- }
- f()
- .then(v => console.log(v))
- .catch(e => console.log(e))
- // Error: 出错了
上面代码中, async 函数 f 执行后, await 后面的 Promise 对象会抛出一个错误对象, 导致 catch 方法的回调函数被调用, 它的参数就是抛出的错误对象. 具体的执行机制, 可以参考后文的 "async 函数的实现".
防止出错的方法, 也是将其放在 try...catch 代码块之中.
- async function f() {
- try {
- await new Promise(function (resolve, reject) {
- throw new Error('出错了');
- });
- } catch(e) {
- }
- return await('hello world');
- }
小结
async 比 generstor 更好, 语法上更语义化, 内置执行器
async 返回值是 Promise, 函数内部的 return 值, 会成为 then 方法回调函数的参数.
await 只能在 async 函数内部使用
如果 await 后面的异步操作出错, 那么等同于 async 函数返回的 Promise 对象被 reject
来源: http://www.qdfuns.com/article/18740/dcfe5856acc87cbfd4c204b0f5f53a8f.html