JavaScript async/await

摘要:在本教程中,您将学习如何使用 JavaScript asyncawait 关键字编写异步代码。

请注意,要了解 async / await 的工作原理,您需要了解 Promise 的工作原理。

JavaScript async / await 关键字简介

在过去,为了处理异步操作,您使用的是 回调函数。但是,嵌套多个回调函数会使您的代码更难维护,从而导致一个臭名昭著的问题,称为回调地狱。

假设您需要按以下顺序执行三个异步操作

  1. 从数据库中选择一个用户。
  2. 从 API 获取用户的服务。
  3. 根据服务器的服务计算服务成本。

以下函数说明了这三个任务。请注意,我们使用 setTimeout() 函数来模拟异步操作。

function getUser(userId, callback) {
    console.log('Get user from the database.');
    setTimeout(() => {
        callback({
            userId: userId,
            username: 'john'
        });
    }, 1000);
}

function getServices(user, callback) {
    console.log(`Get services of  ${user.username} from the API.`);
    setTimeout(() => {
        callback(['Email', 'VPN', 'CDN']);
    }, 2 * 1000);
}

function getServiceCost(services, callback) {
    console.log(`Calculate service costs of ${services}.`);
    setTimeout(() => {
        callback(services.length * 100);
    }, 3 * 1000);
}Code language: JavaScript (javascript)

以下是嵌套回调函数的示例

getUser(100, (user) => {
    getServices(user, (services) => {
        getServiceCost(services, (cost) => {
            console.log(`The service cost is ${cost}`);
        });
    });
});Code language: JavaScript (javascript)

输出

Get user from the database.
Get services of  john from the API.
Calculate service costs of Email,VPN,CDN.
The service cost is 300Code language: JavaScript (javascript)

为了避免这种回调地狱问题,ES6 引入了 Promise,它允许您以更易于管理的方式编写异步代码。

首先,您需要在每个函数中返回一个 Promise

function getUser(userId) {
    return new Promise((resolve, reject) => {
        console.log('Get user from the database.');
        setTimeout(() => {
            resolve({
                userId: userId,
                username: 'john'
            });
        }, 1000);
    })
}

function getServices(user) {
    return new Promise((resolve, reject) => {
        console.log(`Get services of  ${user.username} from the API.`);
        setTimeout(() => {
            resolve(['Email', 'VPN', 'CDN']);
        }, 2 * 1000);
    });
}

function getServiceCost(services) {
    return new Promise((resolve, reject) => {
        console.log(`Calculate service costs of ${services}.`);
        setTimeout(() => {
            resolve(services.length * 100);
        }, 3 * 1000);
    });
}Code language: JavaScript (javascript)

然后,您 对 Promise 进行链式调用

getUser(100)
    .then(getServices)
    .then(getServiceCost)
    .then(console.log);Code language: JavaScript (javascript)

ES2017 引入了 async/await 关键字,它建立在 Promise 之上,允许您编写看起来更像同步代码且更具可读性的异步代码。从技术上讲,async / await 是 Promise 的语法糖。

如果一个函数返回一个 Promise,您可以在函数调用之前添加 await 关键字,如下所示

let result = await f();Code language: JavaScript (javascript)

await 将等待从 f() 返回的 Promise 解决。await 关键字只能在 async 函数内使用。

以下定义了一个 async 函数,它按顺序调用三个异步操作

async function showServiceCost() {
    let user = await getUser(100);
    let services = await getServices(user);
    let cost = await getServiceCost(services);
    console.log(`The service cost is ${cost}`);
}

showServiceCost();Code language: JavaScript (javascript)

如您所见,异步代码现在看起来像同步代码。

让我们深入了解 async / await 关键字。

async 关键字

async 关键字允许您定义一个处理异步操作的函数。

要定义一个 async 函数,您需要在函数关键字之前添加 async 关键字,如下所示

async function sayHi() {
    return 'Hi';
}Code language: JavaScript (javascript)

异步函数通过 事件循环 异步执行。它始终返回一个 Promise

在本例中,由于 sayHi() 函数返回一个 Promise,您可以像这样使用它

sayHi().then(console.log);Code language: JavaScript (javascript)

您也可以从 sayHi() 函数中显式地返回一个 Promise,如以下代码所示

async function sayHi() {
    return Promise.resolve('Hi');
}Code language: JavaScript (javascript)

效果相同。

除了常规函数之外,您还可以在函数表达式中使用 async 关键字

let sayHi = async function () {
    return 'Hi';
}Code language: JavaScript (javascript)

箭头函数:

let sayHi = async () => 'Hi';Code language: JavaScript (javascript)

以及类的 方法

class Greeter {
    async sayHi() {
        return 'Hi';
    }
}Code language: JavaScript (javascript)

await 关键字

您使用 await 关键字来等待一个 Promise 解决,无论是处于已解决状态还是处于拒绝状态。您只能在 async 函数内使用 await 关键字

async function display() {
    let result = await sayHi();
    console.log(result);
}Code language: JavaScript (javascript)

在本例中,await 关键字指示 JavaScript 引擎在显示消息之前等待 sayHi() 函数完成。

请注意,如果您在 async 函数之外使用 await 运算符,您将收到错误。

错误处理

如果一个 Promise 解决,await promise 将返回结果。但是,当 Promise 被拒绝时,await promise 将抛出一个错误,就好像有一个 throw 语句一样。

以下代码

async function getUser(userId) {
     await Promise.reject(new Error('Invalid User Id'));
}Code language: JavaScript (javascript)

… 等同于以下代码

async function getUser(userId) {
    throw new Error('Invalid User Id');
}Code language: JavaScript (javascript)

在实际场景中,Promise 需要一段时间才会抛出错误。

您可以使用 try...catch 语句捕获错误,与常规 throw 语句相同

async function getUser(userId) {
    try {
       const user = await Promise.reject(new Error('Invalid User Id'));
    } catch(error) {
       console.log(error);
    }
}Code language: JavaScript (javascript)

可以捕获由一个或多个 await promise 引起的错误

async function showServiceCost() {
    try {
       let user = await getUser(100);
       let services = await getServices(user);
       let cost = await getServiceCost(services);
       console.log(`The service cost is ${cost}`);
    } catch(error) {
       console.log(error);
    }
}Code language: JavaScript (javascript)

在本教程中,您学习了如何使用 JavaScript async / await 关键字编写看起来像同步代码的异步代码。

本教程对您有帮助吗?