在本教程中,您将学习如何使用 JavaScript   async / await关键词编写异步代码。

请注意,要了解  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);
}

下面展示嵌套的回调函数:

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

输出:

Get user from the database.
Get services of  john from the API.
Calculate service costs of Email,VPN,CDN.
The service cost is 300

为了避免这种回调黑洞问题,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);
    });
}

然后,您将 Promise 进行链式调用:

getUser(100)
    .then(getServices)
    .then(getServiceCost)
    .then(console.log);

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

如果一个函数 f 返回一个 Promise,你可以把 await 关键字放在函数调用的前面,像这样:

let result = await f();

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

下面定义一个 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();

如您所见,异步代码现在看起来像同步代码。让我们深入了解 async / await 关键字。

async 关键词

async 关键字允许您定义处理异步操作的函数。要定义 async 函数,请 async 关键字放在 function 关键字前面,如下所示:

async function sayHi() {
    return 'Hi';
}

异步函数通过事件轮询异步执行。它总是返回一个 Promise.

在此示例中,因为 sayHi() 函数返回一个 Promise,您可以像下面一样的 方式使用它,如下所示:

sayHi().then(console.log);

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

async function sayHi() {
    return Promise.resolve('Hi');
}

效果是一样的。

除了普通函数外,您还可以在函数表达式使用  async 关键字:

let sayHi = async function () {
    return 'Hi';
}

箭头函数

let sayHi = async () => 'Hi'; 

类的方法:

class Greeter {
    async sayHi() {
        return 'Hi';
    }
}

await 关键词

您使用 await 关键字等待 Promise 以解决或拒绝状态解决。您只能在 async 函数内部使用 await 关键字:

async function display() {
    let result = await sayHi();
    console.log(result);
}

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

请注意,如果您在 async 函数外部使用 await,则会抛出错误。

错误处理

如果 await promise 已经得到解决,则返回结果。然而,当 promise 被拒绝时,将 await promise 抛出一个错误,就像有一个 throw 声明一样。

以下代码:

async function getUser(userId) {
     await Promise.reject(new Error('Invalid User Id'));
}

与此相同:

async function getUser(userId) {
    throw new Error('Invalid User Id');
}

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

您可以使用 try...catch 语句来捕获 Promise 的错误,也可以是 throw 的错误:

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

可以捕获由一个或多个 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);
    }
}

结论

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