理解async/await

17,776 阅读3分钟

前言

js有很多异步处理的解决方案,而async/await就是其中的一种,最近有在学习和运用koa,在服务端中,很少用到promise,而是选择了更加优雅的async/await。当然,一种新方案的出现,我们不光需要学会怎么写,也要学会他的原理。

async/await起到什么作用

async/await从字面意思上很好理解,async是异步的意思,await有等待的意思,而两者的用法上也是如此。async用于申明一个function是异步的,而await 用于等待一个异步方法执行完成。

async

async的语法很简单,就是在函数开头加一个关键字,示例如下:

async function f() {
    return 1;
}

async关键字的意思很简单,就是函数返回的是一个promise。

async function f() {
    return 1;
}
f().then((res) => {
    console.log(res)
}) // 1

async函数会返回一个promise对象,如果function中返回的是一个值,async直接会用Promise.resolve()包裹一下返回。

await

关键词await是等待的意思,那么他在等什么呢? 在MDN上写的是:

[return_value] = await expression;

等的是一个表达式,那么表达式,可以是一个常量,变量,promise,函数等。

function getSomething() {
    return "something";
}

async function testAsync() {
    return Promise.resolve("hello async");
}

async function test() {
    const v1 = await getSomething();
    const v2 = await testAsync();
    console.log(v1, v2);
}

test(); // something hello async
  • 为什么await关键词只能在async函数中用

await操作符等的是一个返回的结果,那么如果是同步的情况,那就直接返回了。

那如果是异步的情况呢,异步的情况下,await会阻塞整一个流程,直到结果返回之后,才会继续下面的代码。

阻塞代码是一个很可怕的事情,而async函数,会被包在一个promise中,异步去执行。所以await只能在async函数中使用,如果在正常程序中使用,会造成整个程序阻塞,得不偿失。

async/await中错误处理

promise并不是只有一种resolve,还有一种reject的情况。而await只会等待一个结果,那么发生错误了该怎么处理呢?

  • 用try-catch来做错误捕捉
async function myFunction() {
  try {
    await Promise.reject('1');
  } catch (err) {
    console.log(err);
  }
}
myFunction(); // 1
  • 用promise的catch来做错误捕捉
async function myFunction() {
    await Promise.reject('1').catch((err) => {
        console.log(err);
    });
}
myFunction(); // 1

async/await和promise的区别

promise最大的问题就是在于业务复杂之后,then内部的逻辑也变得复杂,或者循环的异步嵌套场景等,会写出来不那么优美。

我随意列举一个嵌套的例子,然后分别用async/awaitpromise,你就能感受到两者之间的差距:

function takeLongTime(n) {
    return new Promise(resolve => {
        setTimeout(() => resolve(n), n);
    });
}
function step1(n) {
    console.log(`step1 with ${n}`);
    return takeLongTime(n);
}

function step2(m, n) {
    console.log(`step2 with ${m} and ${n}`);
    return takeLongTime(m + n);
}

function step3(k, m, n) {
    console.log(`step3 with ${k}, ${m} and ${n}`);
    return takeLongTime(k + m + n);
}

takeLongTime起到的作用就是延时之后给出延时的数据。

step1代表第一步延时了多久。

step2代表第一步和第二部总共延时了多久。

step3代表第一步、第二步和第三步一共延时了多久。

  • promise版本
function doIt() {
    console.time("doIt");
    const time1 = 300;
    step1(time1)
        .then(time2 => {
            return step2(time1, time2)
                .then(time3 => [time1, time2, time3]);
        })
        .then(times => {
            const [time1, time2, time3] = times;
            return step3(time1, time2, time3);
        })
        .then(result => {
            console.log(`result is ${result}`);
            console.timeEnd("doIt");
        });
}

doIt();
  • async/await版本
async function doIt() {
    console.time("doIt");
    const time1 = 300;
    const time2 = await step1(time1);
    const time3 = await step2(time1, time2);
    const result = await step3(time1, time2, time3);
    console.log(`result is ${result}`);
    console.timeEnd("doIt");
}

doIt();

在这种复杂逻辑中,我们就能发现async/await确实比then链有优势。

总结

async/await是一种异步的解决方案,而koa中支持这一特性,所以,在基于koa来写服务端代码的时候,这种语法糖不可避免,学习和运用它是我们的必修课。