理解async 和 await

推荐查看MDN文档

async 是什么?

async function 声明将定义一个返回 AsyncFunction 对象的异步函数。

一些异步函数变体:

  • 异步函数声明: ` async function foo() {}`
  • 异步函数表达式: const foo = async function () {};
  • 异步函数定义:let obj = { async foo() {} }
  • 异步箭头函数: const foo = async () => {};

我们来看async到底返回了什么:

async function testAsync() {
    return "hello async";
}

const result = testAsync();
console.log(result); // Promise { 'hello async' }

async 函数会返回一个 Promise 对象,如果在函数中return一个直接量,async 会把这个直接量通过 Promise.resolve() 封装成 Promise 对象:

由于返回的是一个promise对象,用原来的方式:then()链来处理这个promise对象:

testAsync().then(v => {
    console.log(v);    // 输出 hello async
});

如果async 函数没有返回值,返回的对象则是: Promise.resolve(undefined)

联想一下Promise 的特点——无等待,所以在没有 await 的情况下执行 async 函数,它会立即执行,返回一个 Promise 对象,并且,绝不会阻塞后面的语句。这和普通返回 Promise 对象的函数并无二致。

很明显,关键在于await

await

语法: ` [return_value] = await expression;`

表达式 : 一个 Promise 对象或者任何要等待的值。 返回值 : 返回 Promise 对象的处理结果。如果等待的不是 Promise 对象,则返回该值本身。

文档描述:

await 表达式会暂停当前 async function 的执行,等待 Promise 处理完成。
若 Promise 正常处理(fulfilled),其回调的resolve函数参数作为 await 表达式的值,继续执行 async function。
若 Promise 处理异常(rejected),await 表达式会把 Promise 的异常原因抛出。
另外,如果 await 操作符后的表达式的值不是一个 Promise,那么该值将被转换为一个已正常处理的 Promise。


简单来说:await 是一个运算符,等到了它要等的东西,一个Promise对象或者其他值,或者异常,这里有三种情况。

  1. 如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是该表达式的值。
async function f2() {
        var y = await 20 * 5;
        console.log(y); // 100
    }
    f2();
  1. 如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。

     function resolveAfter2Seconds(x) {
         return new Promise(resolve => {
             setTimeout(() => {
                 resolve(x);
             }, 2000);
         });
     }
    
     async function f1() {
         var x = await resolveAfter2Seconds(10);
         console.log(x); // 10
         console.log(11)//10 输出后再输出的11
     }
     f1();
    
  2. 如果await 后的 Promise 处理异常,则异常值被抛出。

Promise.reject(reason)方法返回一个用reason拒绝的Promise。

	async function f3() {
        try {
            var z = await Promise.reject(30);
        } catch (e) {
            console.log(e); // 30
        }
    }
    f3();

async/await 的使用

先做个比较

之前说用async 会将其后的函数返回值封装为一个promise对象。

现在用 setTimeout 模拟耗时的异步操作,先来看看不用 async/await 会怎么写:

function takeLongTime() {
    return new Promise(resolve => {
        setTimeout(() => resolve("long_time_value"), 1000);
    });
}

takeLongTime().then(v => {
    console.log("got", v);
});

用 async/await 呢,会是这样:

function takeLongTime() {
    return new Promise(resolve => {
        setTimeout(() => resolve("long_time_value"), 1000);
    });
}

async function test() {
    const v = await takeLongTime();
    console.log(v);
}

test();

这两种方式对异步的处理并没有差别,name使用async的优势在哪?

async/await 的优势在于处理 then 链

如果需要处理由多个 Promise 组成的 then 链的时候:

假设一个业务,分多个步骤完成,每个步骤都是异步的,而且依赖于上一个步骤的结果。我们仍然用 setTimeout 来模拟异步操作:

/**
 * 传入参数 n,表示这个函数执行的时间(毫秒)
 * 执行的结果是 n + 200,这个值将用于下一步骤
 */
function takeLongTime(n) {
    return new Promise(resolve => {
        setTimeout(() => resolve(n + 200), n);
    });
}

function step1(n) {
    console.log(`step1 with ${n}`);
    return takeLongTime(n);
}

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

function step3(n) {
    console.log(`step3 with ${n}`);
    return takeLongTime(n);
}

现在用promise来实现这三个步骤的处理:

function doIt() {
    console.time("doIt");
    const time1 = 300;
    step1(time1)
        .then(time2 => step2(time2))
        .then(time3 => step3(time3))
        .then(result => {
            console.log(`result is ${result}`);
            console.timeEnd("doIt");
        });
}

doIt();


// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
// doIt: 1507.251ms

输出结果 result 是 step3() 的参数 700 + 200 = 900。

doIt() 顺序执行了三个步骤,一共用了 300 + 500 + 700 = 1500 毫秒,和 console.time()/console.timeEnd() 计算的结果一致。

如果用 async/await 来实现呢:

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

doIt();

结果和之前的 Promise 实现是一样的,但是代码结构明显更清晰。

现在把业务要求改一下,仍然是三个步骤,但每一个步骤都需要之前每个步骤的结果。

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);
}

用 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();

// c:\var\test>node --harmony_async_await .
// step1 with 300
// step2 with 800 = 300 + 500
// step3 with 1800 = 300 + 500 + 1000
// result is 2000
// doIt: 2907.387ms

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();

结果一目了然。

参考文章:

理解 JavaScript 的 async/await

async 函数的含义和用法