async函数学习总结

文章目录

  • async定义
  • async基本用法
  • async的语法
    • 返回Promise
    • await语句及错误处理
    • 注意点
  • async 函数的实现原理
  • 顶层await
  • 总结

async定义

  • Generator 函数的语法糖
  • 对Generator函数的改进:
    • 内置执行器
    • 更清晰的语义
    • 更广的适用性
    • 返回值是Promise
  1. 内置执行器:async自带执行器
  2. 更清晰的语义:async表示函数里有异步操作,await表示后面的表达式需要等待结果
  3. 更广的适用性:yield后面只能是Promise或者Thunk,但是await后面可以是promise和原始类型的值
  4. 返回值是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语句捕捉,然后进入下一轮循环

注意点

  1. try/catch处理错误
  2. 非继发关系(互不依赖)的异步操作可使用Promise.all,缩短程序执行时间
  3. await用在嵌套的普通子函数会报错,如forEach,即便加上async也是并发执行,可以用for循环或者reduce来替代,实现继发
  4. 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

你可能感兴趣的:(JS,学习,javascript,前端,es6)