async await用法和原理解读

744 阅读3分钟

1.async await介绍

  • 它是使用了Generator函数基于Promise的封装,是Promise的一个语法糖;
  • 它是一种异步编程的解决方案,可以以同步的代码方式来写异步;
  • await关键字可以“暂停”async function的执行;
  • 可以用try-catch捕获到async function所得到的错误;

2.基本使用

声明两个promise对象:

const promise1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(1)
    }, 500)
})
const promise2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject(2)
    }, 500)
})

传统的方式:

promise1.then(res => { 
    console.log(res); // 1
})
promise2.catch(res => { 
    console.log(res); // 2
})

async await方式

async function asyncFunc(){
    const res = await promise1; 
    console.log(res); // 1

    try{
      const res = await promise2;
    } catch(err) {
      console.log(err); // 2
    }
}
asyncFunc();

3.进阶使用

场景:要做三件事,下一件事依赖上一件事返回的结果;
假设:dosomething返回的是Promise;
处理顺序:dosomething1 => dosomething2 => dosomething3;

// 传统的方式 (可以看到存在多重嵌套,错误处理也需要单独写)
dosomething1().then(res1 => {
  dosomething2(res1).then(res2 => {
    dosomething3(res2).then(res3 => {
      console.log(res3)
    })
  })
})

// async await方式 (没了嵌套,简洁了)
try {
  const res1 = await dosomething1();
  const res2 = await dosomething1(res1);
  const res3 = await dosomething1(res2);
  console.log(res3)
} catch (error) {
  console.log(error) // 统一捕获错误
}

注意,如果每件事没有相互之间的依赖,使用了上面的那种方式后,会增加得到结果的时间(明明可以并行处理的,但是变成了串行);可以考虑使用Promise.all来执行:

const [res1, res2, res3] = await Promise.all([dosomething1(), dosomething2(), dosomething3()])

4.原理解读

Generator函数

Generator 函数,可以通过 yield 关键字,把函数的执行流挂起,通过next()方法可以切换到下一个状态,可以控制代码执行流程(暂停和继续),从而为异步编程提供解决方案。

基本使用:

function* myGenerator() {
  yield '1'
  yield '2'
  return '3' // 到return这步,done为true;
}

const gen = myGenerator();  // 获取迭代器
gen.next()  //{value: "1", done: false}
gen.next()  //{value: "2", done: false}
gen.next()  //{value: "3", done: true}

可以通过给next()传参, 让yield具有返回值:

function* myGenerator() {
  console.log(yield '1')  //res1
  console.log(yield '2')  //res2
  console.log(yield '3')  //res3
}

// 获取迭代器
const gen = myGenerator();

gen.next()
gen.next('res1')
gen.next('res2')
gen.next('res3')

await async的规范

  • async函数会自动返回一个Promise对象;
  • await关键字能够返回Promise的resolve的值;
  • await关键字必须用作async函数内;

实现思路

  • 相同:可以看到 */yield和async/await这两个关键词有点类似的;
  • 不同:await每一步可以自动执行,不需要手动调用next()就能自动执行下一步;
  • 不同:asnyc返回的是一个Promise, Generator函数返回的是一个迭代器对象;

所以我们需要封装一个返回Promise对象的并且可以自动执行的Generator函数的函数:

//通过包一层runAsync函数 模拟await async
function* asyncFn() {
  try {
    var a = yield 1; // next()返回 { value: 1, done: false }
    var b = yield Promise.resolve(++a); // next()返回 { value: Promise.resolve(++a), done: false }
    //throw Error();
    var c = yield Promise.resolve(++b); // next()返回 { value: Promise.resolve(++b), done: false }
    console.log(c); // 3
    return c; // next()返回 { value: 3, done: true }
  } catch (error) {
    console.log(error,"error")
  }
}

function runAsync(asyncFn) {
  let g = asyncFn();
  // async返回的是一个Promise
  return new Promise((resolve, reject) => {
    // 实现自动执行的方法
    function _next(val) {
       try {
         var res = g.next(val); //得到Generator对象: {value: xxx, done: xxx} 
       } catch (error) {
         // 防止yield执行过程成抛出错误
         reject(error);
         return;
       }
      // 执行到最后;退出自动执行
      if (res.done) return resolve(res.value);
      
      // 自动执行下一个yield
      // 包一层Promise是为了兼容yield后面不是跟Promise对象的情况;
      Promise.resolve(res.value).then(
        (data) => {
          _next(data);
        },
        (err) => {
          g.throw(err); // 给外面的try catch捕获
        }
      );
    }

    _next();
  });
}

// 执行asyncFn,模拟的async await函数执行流程;
let p = runAsync(asyncFn);
p.then(res => {
  console.log(res) // 3
}).catch((err) => {
   console.log(err);
});

babel编译后的结果也是类似的:链接

参考文章: juejin.cn/post/684490…