全部 / 前端 / 技术 · 2022年4月7日 0

38 – Async/Await: 常见的错误

在我们舒服的在代码中使用 await 的之前,我们需要认识到一些我们不能做的:

  1. 在没有 async 标注的函数中使用 await;
  2. 在顶层使用 await;

第一个我们在之前的文章已经讨论过,那第二个我们接下来介绍:

async function wait(message, time) {
    return new Promise((resolve) => setTimeout(resolve(message), time));
}

await wait ("hello", 2000); // SyntaxError: await is only allows inside an async function

我们可以重写使其正常工作:

async function wait(message, time) {
    return new Promise((resolve) => setTimeout(resolve(message), time));
}

async function execute() {
    const message = await wait ("hello", 2000);
    console.log(message); // hello (after 2000 ms)
}

execute();

顶层执行 await 的提案在这且V8已经支持

意外的使代码同步

async/await 最大的为题在于 await 很容易被滥用,我们多数情况下是系统代码异步执行使得代码更高效。

为了帮助我们理解这个问题,我来举一个 promise 的例子,然后把它转为 async/await 接着改正它:

const sayGreeting = (name, time) => {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(`Hello ${name}`);
        }, time);
    })
}

sayGreeting("Parwinder", 1000)
    .then((data) => {
        console.log(data); // "Hello Parwinder" after 1 second
        return sayGreeting("Lauren", 2000);
    })
    .then((data) => {
        console.log(data); // "Hello Lauren" after 2 seconds
        return sayGreeting("Robert", 500);
    })
    .then((data) => {
        console.log(data); // "Hello Robert" after half a second
        return sayGreeting("Eliu", 2000);
    })
    .then((data) => {
        console.log(data); // "Hello Eliu" after 2 seconds
        return sayGreeting("George", 1500);
    })
    .then((data) => {
        console.log(data); // "Hello George" after 1.5 seconds
    })

上面的例子是在指定时间后想某个人打招呼,Promise 避免了 callback 的回调地狱,但依旧存在一层回调。

我们可以使用 await 来重写:

const sayGreeting = (name, time) => {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(`Hello ${name}`);
        }, time);
    })
}

const main = async () => {
    let a = await sayGreeting("Parwinder", 1000);
    console.log(a); // "Hello Parwinder" after 1 second
    a = await sayGreeting("Lauren", 2000);
    console.log(a); // "Hello Lauren" after 2 seconds
    a = await sayGreeting("Robert", 500);
    console.log(a); // "Hello Robert" after half a second
    a = await sayGreeting("Eliu", 2000);
    console.log(a); // "Hello Eliu" after 2 seconds
    a = await sayGreeting("George", 1500);
    console.log(a); // "Hello George" after 1.5 seconds
}

main();

没有了 then 的回调且更容易阅读,目前为止我们把 promise 改为了 async/await 而且代码看上去更好,错误在哪里呢?

多数情况下,我们期望异步是并行执行。每次在 main 函数中添加一个 await 就会阻止代码向下执行直到 promise 完成。我们可以同时执行多个 promise 且取得执行结果。

我们使用 Promise.all 与 async/await 一起使得代码更高效:

const sayGreeting = (name, time) => {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(`Hello ${name}`);
        }, time);
    })
}

const main = async () => {
    const a = sayGreeting("Parwinder", 1000);
    const b = sayGreeting("Lauren", 2000);
    const c = sayGreeting("Robert", 500);
    const d = sayGreeting("Eliu", 2000);
    const e = sayGreeting("George", 1500);
    const [greeting1, greeting2, greeting3, greeting4, greeting5] = await Promise.all([a, b, c, d, e]);
    // all promises in promise.all
    console.log(greeting1, greeting2, greeting3, greeting4, greeting5)
}

main();

我们做了什么:

  1. 我们把 promise 存在变量里,而不是等待 promise 完成;
  2. 我们创建了一个巨大的 promise 集合;
  3. 我们 await 这个 Promise.all 而不是独立的 promise;
  4. Promise.all 同时执行所有的 promise ,并等待所有结果返回并赋值给对应的变量;
  5. 打印输出。

希望可以提高你使用 async/await 的能力。