async函数学习总结
文章目录
- async定义
- async基本用法
- async的语法
- 返回Promise
- await语句及错误处理
- 注意点
- async 函数的实现原理
- 顶层await
- 总结
async定义
- Generator 函数的语法糖
- 对Generator函数的改进:
- 内置执行器
- 更清晰的语义
- 更广的适用性
- 返回值是Promise
- 内置执行器:async自带执行器
- 更清晰的语义:async表示函数里有异步操作,await表示后面的表达式需要等待结果
- 更广的适用性:yield后面只能是Promise或者Thunk,但是await后面可以是promise和原始类型的值
- 返回值是Promise:async函数的返回值是Promise对象,可以用then指定下一步操作
注意:await后面如果是原始类型的值会自动转换为立即resolved的Promise对象
async基本用法
- async函数被调用的时候会立即返回一个pending状态的Promise对象
- 等async内部所有异步操作执行完才会发生状态改变,才会执行then回调(除非遇到return或者抛出错误)
- async函数返回的也是promise,所以可以作为await的参数
- async函数也可以用在对象的方法或者class的方法
async的语法
- 难点:错误处理机制
返回Promise
- async函数内部return语句返回的值,会成为then方法回调函数的参数
- async函数内部抛出错误,可以通过then的第二个参数或者链式调用catch接收
async fn() {
throw new Error('错误!!!')
}
fn().then(
v => console.log('resolve-', v),
e => console.log('reject-:', e)
)
// 或者
fn().then(
v => console.log('resolve-', v)
).catch(
e => console.log('reject-', e)
)
返回的Promise对象需要等async内部所有异步操作执行完才会发生状态改变,才会执行then回调(除非遇到return或者抛出错误)
await语句及错误处理
- 后面是Promise,返回该对象的结果,比如里面resolve(111), 那么await的返回值就是111
- 后面不是Promise,返回对应的值,比如await 123,返回123
- 后面是thenable对象(定义了then方法的对象),等同于Promise的处理
- 任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行,reject的参数不需要return也会被传入catch回调
- 不中断后续异步操作解决方案:
- 将可能reject的多个await语句放在try/catch里面
- 在每个可能reject的await语句后面加上catch处理错误
// 实现多次重复尝试
const superagent = require('superagent');
const NUM_RETRIES = 3;
async function test() {
let i;
for (i = 0; i < NUM_RETRIES; ++i) {
try {
await superagent.get('http://google.com/this-throws-an-error');
break;
} catch(err) {}
}
console.log(i); // 3
}
test();
// 如果await操作成功,就会使用break语句退出循环;
// 如果失败,会被catch语句捕捉,然后进入下一轮循环
注意点
- try/catch处理错误
- 非继发关系(互不依赖)的异步操作可使用Promise.all,缩短程序执行时间
- await用在嵌套的普通子函数会报错,如forEach,即便加上async也是并发执行,可以用for循环或者reduce来替代,实现继发
- async 函数可以保留运行堆栈
// 独立异步同步触发操作写法(写成继发关系会比较耗时)
// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
// reduce解决嵌套子函数问题
async function dbFuc(db) {
let docs = [{}, {}, {}];
await docs.reduce(async (_, doc) => {
await _;
await db.post(doc);
}, undefined);
}
// reduce()方法的第一个参数是async函数,导致该函数的第一个参数是前一步操作返回的 Promise 对象,所以必须使用await等待它操作结束
// 另外,reduce()方法返回的是docs数组最后一个成员的async函数的执行结果,也是一个 Promise 对象,导致在它前面也必须加上await
async 函数的实现原理
- Generator 函数和自动执行器,包装在一个函数里
// 自动执行器
function spawn(genF) {
return new Promise(function(resolve, reject) {
const gen = genF();
function step(nextF) {
let next;
try {
next = nextF();
} catch(e) {
return reject(e);
}
if(next.done) {
return resolve(next.value);
}
Promise.resolve(next.value).then(function(v) {
step(function() { return gen.next(v); });
}, function(e) {
step(function() { return gen.throw(e); });
});
}
step(function() { return gen.next(undefined); });
});
}
顶层await
- 早期的语法规定是:await命令独立使用会报错
- 从 ES2022 开始,允许在模块的顶层独立使用await命令
- 主要目的是使用await解决模块异步加载的问题
- 注意,顶层await只能用在 ES6 模块,不能用在 CommonJS 模块
- 因为 CommonJS 模块的require()是同步加载,如果有顶层await,就没法处理加载了
- 注意,如果加载多个包含顶层await命令的模块,加载命令是同步执行的,某个模块遇到异步会先执行其他模块,等结果拿到后在继续执行
总结
- async函数是Generator 函数的语法糖,但是性能更优,体现在自动执行、语义清晰、适用更广
- async返回一个Promise,可以then回调处理,catch捕获错误
- async函数被调用的时候会立即返回一个pending状态的Promise对象
- 等async内部所有异步操作执行完才会发生状态改变,才会执行then回调(除非遇到return或者抛出错误)
- await后异步操作如果reject会中断后续操作,可以通过try/catch包含块处理错误
- await后面如果是原始类型的值会自动转换为立即resolved的Promise对象
- 非继发关系(互不依赖)的异步操作可使用Promise.all,缩短程序执行时间
- await用在嵌套的普通子函数会报错,如forEach,即便加上async也是并发执行,可以用for循环或者reduce来替代,实现继发
- ES2022后可以顶层独立使用await