JavaScript await表达式的使用场景
异步操作与 Promise
在深入探讨 await
表达式的使用场景之前,我们先来回顾一下 JavaScript 中的异步操作和 Promise
。
JavaScript 是一门单线程语言,这意味着它在同一时间只能执行一个任务。然而,在现代 web 开发中,许多操作是异步的,比如网络请求、读取文件等。这些操作可能需要一些时间才能完成,如果采用同步方式执行,会阻塞主线程,导致用户界面失去响应。
为了解决这个问题,JavaScript 引入了异步操作的概念。Promise
是 JavaScript 处理异步操作的一种方式,它代表了一个尚未完成但预期将来会完成的操作。Promise
有三种状态:pending
(进行中)、fulfilled
(已成功)和 rejected
(已失败)。
创建 Promise
我们可以使用 new Promise()
构造函数来创建一个 Promise
。构造函数接受一个执行器函数作为参数,执行器函数接受两个回调函数 resolve
和 reject
。当异步操作成功时,调用 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
都被解决后返回一个新的 Promise
。await
可以与 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
数组作为参数,并返回一个新的 Promise
。await
暂停 asyncFunction
的执行,直到 Promise.all
返回的 Promise
被解决。当所有传入 Promise.all
的 Promise
都被解决后,await
表达式返回一个包含所有解决值的数组。
await 与 Promise.race
Promise.race
与 Promise.all
类似,但它会在第一个 Promise
被解决或被拒绝时就返回一个新的 Promise
。await
同样可以与 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
表达式,可以帮助我们更好地处理复杂的异步逻辑,构建出更健壮、高效的应用程序。