async 函数
async
函数的返回值
return
Promise 实例
async function show() {
return Promise.resolve("ok");
}
let p1 = show();
console.log("p1", p1); // p1 Promise { <pending> } —— 同步获取 (获取失败)
setTimeout(() => {
console.log("p1", p1); // p1 Promise {<fulfilled>: 'ok'} —— 异步获取 (宏任务)
}, 0);
p1.then(val => {
console.log("val", val); // val ok —— 异步获取 (微任务)
});
输出顺序:p1 Promise { <pending> }
→ val ok
→ p1 Promise {<fulfilled>: 'ok'}
return
非 Promise 实例,则得到一个 fulfilled 的 Promise 实例,该 Promise 实例的值为return
的数据
async function show() {
return 1; // 相当于 return Promise.resolve(数据值)
}
let p1 = show();
console.log("p1", p1); // p1 Promise {<fulfilled>: 1} —— 同步获取
p1.then(val => {
console.log("val", val); // val 1 —— 异步获取
});
- 抛出异常,得到 rejected 的 Promise 实例,该 Promise 实例的值就是抛出的异常数据
async function show() {
throw "出错了";
}
let p1 = show();
console.log("p1", p1); // p1 Promise {<rejected>: '出错了'} —— 同步获取
p1.catch(reason => {
console.log("reason", reason); // reason 出错了 —— 异步获取
});
async
函数的写法
async function fun1() {} // 普通函数
let fun2 = async function () {}; // 函数表达式
let fun3 = async () => {}; // 箭头函数
let obj = {
name: "superman",
async showName() {}, // 对象方法
};
await 关键字
-
我们可以在 async 函数的直接子作用域中,使用
await
关键字来控制函数的执行 -
await
同一行后面的内容对应 Promise 主体内容,即同步执行的;await
下一行的内容对应then()
里面的内容,是异步执行的 -
所以,
async
函数中,第一个await
之前的代码会同步执行,之后的代码会异步执行
async function getData() {
const data = await axios.get(url);
return data;
}
// 等效于
function getData() {
return axios.get(url).then(data => {
return data;
});
}
await
同一行后面应该跟着一个 Promise 对象;如果不是,需要转换(如果是常量会自动转换)
比如下面写法就是不正确的:
async function getData() {
// await 不认识后面的 setTimeout, 不知道何时返回
const data = await setTimeout(() => {
return;
}, 3000);
console.log("3 秒到了");
}
正确写法是:
async function getData() {
const data = await new Promise(resolve => {
setTimeout(() => {
resolve();
}, 3000);
});
console.log("3 秒到了");
}
- 如果
await
右侧的数据是非 Promise 实例,则会将其转为 Promise 实例,数据值为该 Promise 实例的值;
然后将该 Promise 实例的值赋值给左侧的变量 - 如果
await
的右侧为 Promise 实例时,只要 Promise 实例的状态没改变,函数就会挂起等待;
若 Promise 实例状态变成 fulfilled,则将该 Promise 实例的值赋给 await 左侧的变量,然后继续往下执行程序
async function test() {
console.log("async");
let val1 = await new Promise(resolve => {
setTimeout(() => {
resolve("ProResolve");
}, 1000);
});
console.log(val1);
let val2 = await "普通数据";
console.log(val2);
}
console.log("before await");
test();
console.log("after await");
上例的输出顺序是 before await
- async
- after await
- ProResolve
- 普通数据
- 上例等效于:
console.log("before await");
new Promise(resolve => {
console.log("async");
setTimeout(() => {
resolve("ProResolve");
}, 1000);
})
.then(val1 => {
console.log(val1);
return "普通数据";
})
.then(val2 => console.log(val2));
console.log("after await");
- 如果
await
右侧的是 rejected 状态的 Promise 实例,就会抛出异常:
需要通过try … catch
语句捕获并处理错误
也可以使用 Promise 实例调用 catch 方法处理错误
async function show() {
let res = await Promise.reject("wrong"); // 这里抛出错误,后面的语句都不会执行
console.log("res", res); // 这里不会打印
}
show();
async function show() {
let res = await Promise.reject("wrong").catch(reason => {
console.log("reason", reason); // reason wrong
});
console.log("res", res); // res undefined
}
show();
async function show() {
try {
let res = await Promise.reject("wrong");
console.log("res", res); // 这里不会打印
} catch (error) {
console.log("error", error); // error wrong
}
}
show();
- 如果不用 await 关键字,也不 return Promise 实例,async 函数与普通函数一样
async function test() {
console.log("async");
return 1;
}
console.log("before async");
const res = test();
console.log("res", res);
console.log("after async");
上例的输出顺序是 before async
→ async
→ res Promise {<fulfilled>: 1}
→ after async
await 使用陷阱
- 对于并行执行的异步任务,其实并不适合用 await 修饰
async function fun() {
let pro1 = await fetch("url1");
let pro2 = await fetch("url2");
// ...
}
上例这样写,虽没有错误,但会打破两个 fetch 的并行,这需要等待第 1 个异步任务执行完毕之后,才会执行第 2 个异步任务
所以我们可以不用 await 修饰这些并行执行的异步操作
async function fun() {
let proA = fetch("url1"); // 只要不加 await,Ajax 就会同时请求
let proB = fetch("url2");
let pro1 = await proA;
let pro2 = await proB;
console.log(pro1, pro2);
// ...
}
这里更高效的做法:通过 Promise.all
组合起来
async function fun() {
let proA = fetch("url1"); // 只要不加 await,Ajax 就会同时请求
let proB = fetch("url2");
let [pro1, pro2] = await Promise.all([proA, proB]);
// ...
}
- 如果我们需要在循环中执行异步操作,是不能够直接调用
forEach
/map
这一类方法的,尽管我们在回调函数中写了await
,但这里的forEach
会立刻返回,并不会暂停等到所有异步操作都执行完毕
async function fun() {
[1, 2, 3].forEach(async i => {
await someAsyncOperation();
});
console.log("done");
}
fun();
如果我们希望等待循环中的异步操作都完成之后才继续执行,那我们还是应该使用传统的 for
循环
async function fun() {
for (let i of [1, 2, 3]) {
await someAsyncOperation();
}
console.log("done");
}
fun();
更进一步,如果我们想所有操作都并发执行,可以使用 for await
:
这里的 for
循环依然会等到所有的异步操作都完成之后才继续向后执行
async function fun() {
let pros = [someAsyncOperation(), someAsyncOperation(), someAsyncOperation()];
for await (let result of pros) {
// ...
}
console.log("done");
}
fun();
案例
使用回调函数读取多个文件
const fs = require('fs');
fs.readFile('./demo.html', (err, data1) => {
if (err) throw err;
fs.readFile('./demo.html', (err, data2) => {
if (err) throw err;
fs.readFile('./demo.html', (err, data3) => {
if (err) throw err;
console.log(data1 + data2 + data3);
});
});
});
使用 async
& await
读取多个文件
const fs = require('fs');
const util = require('util');
// 把 fs.readFile 方法封装成 Promise 实例
let myReadFile = util.promisify(fs.readFile);
async function main() {
try {
let data1 = await myReadFile('./demo.html');
let data2 = await myReadFile('./demo.html');
let data3 = await myReadFile('./demo.html');
console.log(data1 + data2 + data3);
} catch (error) {
console.log(error);
}
}
main();