MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

JavaScript await表达式的使用场景

2021-11-072.9k 阅读

异步操作与 Promise

在深入探讨 await 表达式的使用场景之前,我们先来回顾一下 JavaScript 中的异步操作和 Promise

JavaScript 是一门单线程语言,这意味着它在同一时间只能执行一个任务。然而,在现代 web 开发中,许多操作是异步的,比如网络请求、读取文件等。这些操作可能需要一些时间才能完成,如果采用同步方式执行,会阻塞主线程,导致用户界面失去响应。

为了解决这个问题,JavaScript 引入了异步操作的概念。Promise 是 JavaScript 处理异步操作的一种方式,它代表了一个尚未完成但预期将来会完成的操作。Promise 有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。

创建 Promise

我们可以使用 new Promise() 构造函数来创建一个 Promise。构造函数接受一个执行器函数作为参数,执行器函数接受两个回调函数 resolvereject。当异步操作成功时,调用 resolve 并传入成功的值;当异步操作失败时,调用 reject 并传入错误信息。

const myPromise = new Promise((resolve, reject) => {
    setTimeout(() => {
        const success = true;
        if (success) {
            resolve('操作成功');
        } else {
            reject('操作失败');
        }
    }, 1000);
});

处理 Promise

一旦创建了 Promise,我们可以使用 .then().catch().finally() 方法来处理它的不同状态。

.then() 方法用于处理 Promise 成功的情况,它接受一个回调函数,该回调函数的参数是 Promise 成功时返回的值。

myPromise.then((result) => {
    console.log(result); // 输出: 操作成功
});

.catch() 方法用于处理 Promise 失败的情况,它接受一个回调函数,该回调函数的参数是 Promise 失败时返回的错误信息。

myPromise.catch((error) => {
    console.error(error); // 输出: 操作失败
});

.finally() 方法无论 Promise 是成功还是失败都会执行,它不接受任何参数。

myPromise.finally(() => {
    console.log('Promise 操作结束');
});

async 函数

async 函数是 JavaScript 中用于处理异步操作的另一种语法糖,它基于 Promise 构建,使得异步代码看起来更像同步代码,提高了代码的可读性和可维护性。

定义 async 函数

async 函数的定义非常简单,只需要在函数声明前加上 async 关键字。

async function asyncFunction() {
    return '这是一个异步函数的返回值';
}

async 函数的返回值

async 函数始终返回一个 Promise。如果 async 函数的返回值不是一个 Promise,JavaScript 会自动将其包装成一个已解决(resolved)的 Promise

async function asyncFunction() {
    return '直接返回字符串';
}

asyncFunction().then((result) => {
    console.log(result); // 输出: 直接返回字符串
});

如果 async 函数抛出一个错误,返回的 Promise 会被拒绝(rejected)。

async function asyncFunction() {
    throw new Error('这是一个错误');
}

asyncFunction().catch((error) => {
    console.error(error.message); // 输出: 这是一个错误
});

await 表达式

await 表达式只能在 async 函数内部使用,它用于暂停 async 函数的执行,直到 await 后面的 Promise 被解决(resolved)或被拒绝(rejected)。

基本用法

async function asyncFunction() {
    const promise = new Promise((resolve) => {
        setTimeout(() => {
            resolve('Promise 已解决');
        }, 1000);
    });

    const result = await promise;
    console.log(result); // 输出: Promise 已解决
}

asyncFunction();

在上述代码中,await promise 暂停了 asyncFunction 的执行,直到 promise 被解决。当 promise 被解决后,await 表达式返回 promise 的解决值,并继续执行 asyncFunction 中的后续代码。

await 与 Promise.all

Promise.all 用于并行执行多个 Promise,并在所有 Promise 都被解决后返回一个新的 Promiseawait 可以与 Promise.all 结合使用,以等待所有并行的 Promise 完成。

async function asyncFunction() {
    const promise1 = new Promise((resolve) => {
        setTimeout(() => {
            resolve('Promise 1 已解决');
        }, 1000);
    });

    const promise2 = new Promise((resolve) => {
        setTimeout(() => {
            resolve('Promise 2 已解决');
        }, 1500);
    });

    const results = await Promise.all([promise1, promise2]);
    console.log(results); // 输出: ['Promise 1 已解决', 'Promise 2 已解决']
}

asyncFunction();

在这个例子中,Promise.all 接受一个 Promise 数组作为参数,并返回一个新的 Promiseawait 暂停 asyncFunction 的执行,直到 Promise.all 返回的 Promise 被解决。当所有传入 Promise.allPromise 都被解决后,await 表达式返回一个包含所有解决值的数组。

await 与 Promise.race

Promise.racePromise.all 类似,但它会在第一个 Promise 被解决或被拒绝时就返回一个新的 Promiseawait 同样可以与 Promise.race 结合使用。

async function asyncFunction() {
    const promise1 = new Promise((resolve) => {
        setTimeout(() => {
            resolve('Promise 1 已解决');
        }, 1000);
    });

    const promise2 = new Promise((resolve) => {
        setTimeout(() => {
            resolve('Promise 2 已解决');
        }, 1500);
    });

    const result = await Promise.race([promise1, promise2]);
    console.log(result); // 输出: Promise 1 已解决
}

asyncFunction();

在这个例子中,Promise.race 接受一个 Promise 数组作为参数。由于 promise1 先于 promise2 被解决,await 表达式返回 promise1 的解决值。

await 表达式的使用场景

网络请求

在 web 开发中,网络请求是最常见的异步操作之一。await 表达式使得处理网络请求变得非常直观。

使用 Fetch API

Fetch API 是现代 JavaScript 用于进行网络请求的标准接口,它返回一个 Promise。我们可以使用 await 来等待网络请求完成。

async function fetchData() {
    try {
        const response = await fetch('https://example.com/api/data');
        if (!response.ok) {
            throw new Error('网络请求失败');
        }
        const data = await response.json();
        console.log(data);
    } catch (error) {
        console.error(error.message);
    }
}

fetchData();

在上述代码中,await fetch('https://example.com/api/data') 等待网络请求完成,并返回一个 Response 对象。我们可以检查 response.ok 来判断请求是否成功,如果失败则抛出一个错误。然后,await response.json() 等待将响应数据解析为 JSON 格式,并返回解析后的数据。

使用 Axios

Axios 是一个流行的基于 Promise 的 HTTP 客户端库,它也可以与 await 很好地配合使用。

import axios from 'axios';

async function fetchData() {
    try {
        const response = await axios.get('https://example.com/api/data');
        console.log(response.data);
    } catch (error) {
        console.error(error.message);
    }
}

fetchData();

文件操作

在 Node.js 环境中,文件操作也是异步的。fs 模块提供了异步文件操作的方法,这些方法返回 Promise,可以与 await 结合使用。

读取文件

const fs = require('fs').promises;

async function readFileContent() {
    try {
        const content = await fs.readFile('example.txt', 'utf8');
        console.log(content);
    } catch (error) {
        console.error(error.message);
    }
}

readFileContent();

在这个例子中,await fs.readFile('example.txt', 'utf8') 等待文件读取操作完成,并返回文件的内容。

写入文件

const fs = require('fs').promises;

async function writeFileContent() {
    try {
        await fs.writeFile('example.txt', '这是要写入的内容');
        console.log('文件写入成功');
    } catch (error) {
        console.error(error.message);
    }
}

writeFileContent();

数据库操作

在使用数据库时,如 MySQL、MongoDB 等,操作通常是异步的。不同的数据库驱动库提供了基于 Promise 的 API,我们可以使用 await 来处理这些异步操作。

MySQL 操作

mysql2 库为例:

const mysql = require('mysql2/promise');

async function queryDatabase() {
    try {
        const connection = await mysql.createConnection({
            host: 'localhost',
            user: 'root',
            password: 'password',
            database: 'test'
        });

        const [rows] = await connection.execute('SELECT * FROM users');
        console.log(rows);

        await connection.end();
    } catch (error) {
        console.error(error.message);
    }
}

queryDatabase();

在上述代码中,await mysql.createConnection(...) 等待数据库连接建立,await connection.execute('SELECT * FROM users') 等待查询执行并返回结果,await connection.end() 等待数据库连接关闭。

MongoDB 操作

mongodb 库为例:

const { MongoClient } = require('mongodb');

async function queryDatabase() {
    try {
        const uri = "mongodb://localhost:27017";
        const client = new MongoClient(uri);
        await client.connect();

        const database = client.db('test');
        const collection = database.collection('users');
        const results = await collection.find({}).toArray();
        console.log(results);

        await client.close();
    } catch (error) {
        console.error(error.message);
    }
}

queryDatabase();

延迟操作

有时候我们需要在代码中引入延迟,await 可以与 new Promise 结合来实现延迟操作。

async function delay(ms) {
    return new Promise((resolve) => {
        setTimeout(resolve, ms);
    });
}

async function delayedOperation() {
    console.log('开始操作');
    await delay(2000);
    console.log('延迟 2 秒后操作');
}

delayedOperation();

在这个例子中,delay 函数返回一个 Promise,该 Promise 在指定的毫秒数后被解决。await delay(2000) 暂停 delayedOperation 函数的执行,延迟 2 秒后继续执行后续代码。

处理多个异步操作的顺序

在实际开发中,我们经常需要按顺序执行多个异步操作。await 表达式可以很方便地实现这一点。

async function stepByStep() {
    console.log('开始第一步');
    await firstAsyncOperation();
    console.log('第一步完成,开始第二步');
    await secondAsyncOperation();
    console.log('第二步完成,操作结束');
}

function firstAsyncOperation() {
    return new Promise((resolve) => {
        setTimeout(() => {
            console.log('第一步操作完成');
            resolve();
        }, 1000);
    });
}

function secondAsyncOperation() {
    return new Promise((resolve) => {
        setTimeout(() => {
            console.log('第二步操作完成');
            resolve();
        }, 1500);
    });
}

stepByStep();

在这个例子中,await firstAsyncOperation() 等待第一步异步操作完成后,才会执行 await secondAsyncOperation(),从而确保了异步操作按顺序执行。

错误处理

await 表达式与 try...catch 块结合使用,可以非常方便地处理异步操作中的错误。

async function asyncFunctionWithError() {
    try {
        const promise = new Promise((_, reject) => {
            setTimeout(() => {
                reject('这是一个错误');
            }, 1000);
        });

        const result = await promise;
        console.log(result);
    } catch (error) {
        console.error(error.message); // 输出: 这是一个错误
    }
}

asyncFunctionWithError();

在上述代码中,当 promise 被拒绝时,await 表达式会抛出错误,该错误会被 try...catch 块捕获并处理。

处理递归异步操作

递归是一种强大的编程技术,在异步场景下,await 可以帮助我们处理递归的异步操作。

async function recursiveAsyncOperation(n) {
    if (n <= 0) {
        return;
    }

    console.log(`开始第 ${n} 次操作`);
    await new Promise((resolve) => {
        setTimeout(() => {
            console.log(`第 ${n} 次操作完成`);
            resolve();
        }, 1000);
    });

    await recursiveAsyncOperation(n - 1);
    console.log(`第 ${n} 次递归操作结束`);
}

recursiveAsyncOperation(3);

在这个例子中,recursiveAsyncOperation 函数通过 await 等待每次异步操作完成后再进行下一次递归,确保了递归过程中的异步操作按顺序执行。

与事件驱动编程结合

在一些场景下,我们可能需要将 await 与事件驱动编程结合使用。例如,在 Node.js 中处理 HTTP 服务器的请求事件。

const http = require('http');
const { promisify } = require('util');

const server = http.createServer(async (req, res) => {
    try {
        const chunks = [];
        for await (const chunk of req) {
            chunks.push(chunk);
        }
        const body = Buffer.concat(chunks).toString('utf8');
        console.log('接收到请求体:', body);
        res.end('请求已处理');
    } catch (error) {
        console.error(error.message);
        res.statusCode = 500;
        res.end('服务器错误');
    }
});

const port = 3000;
server.listen(port, async () => {
    try {
        await promisify(setTimeout)(2000);
        console.log(`服务器在端口 ${port} 上运行`);
    } catch (error) {
        console.error(error.message);
    }
});

在上述代码中,for await...of 循环用于异步读取请求流的数据。await promisify(setTimeout)(2000) 用于在服务器启动后延迟 2 秒输出日志。

在测试框架中的应用

在测试框架如 Jest 中,await 可以用于处理异步测试用例。

const axios = require('axios');

test('异步测试用例', async () => {
    try {
        const response = await axios.get('https://example.com/api/data');
        expect(response.status).toBe(200);
    } catch (error) {
        fail(error.message);
    }
});

在这个测试用例中,await axios.get('https://example.com/api/data') 等待网络请求完成,然后使用 expect 进行断言。如果请求失败,catch 块会捕获错误并使用 fail 标记测试用例失败。

总结

await 表达式是 JavaScript 中处理异步操作的强大工具,它在网络请求、文件操作、数据库操作、延迟操作、错误处理等众多场景中都发挥着重要作用。通过与 async 函数结合使用,await 使得异步代码更易于阅读、编写和维护,提高了开发效率。在实际开发中,深入理解并熟练运用 await 表达式,可以帮助我们更好地处理复杂的异步逻辑,构建出更健壮、高效的应用程序。