async/await语法糖
async/await的本质
async/await是Generator函数的语法糖,底层基于Promise实现。它让异步代码看起来像同步代码,但实际执行过程仍然是异步的。async函数返回一个Promise对象,await命令后面通常是一个Promise对象。
async function fetchData() {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return data;
}
async函数的基本用法
在函数声明前加上async关键字,就变成了async函数。async函数内部可以使用await表达式,await会暂停async函数的执行,等待Promise解决后再继续执行。
async function getUser(id) {
try {
const response = await fetch(`/users/${id}`);
if (!response.ok) {
throw new Error('Network response was not ok');
}
return await response.json();
} catch (error) {
console.error('Fetch error:', error);
}
}
await表达式的行为
await表达式会暂停async函数的执行,等待Promise解决。如果等待的不是Promise对象,则直接返回该值本身。await只能在async函数内部使用。
async function process() {
const value1 = await 42; // 非Promise值直接返回
const value2 = await Promise.resolve('hello');
console.log(value1, value2); // 42 "hello"
}
错误处理机制
async函数内部可以使用try/catch捕获错误,也可以使用Promise的catch方法处理错误。未捕获的错误会导致返回的Promise被reject。
// 使用try/catch
async function fetchWithRetry(url, retries = 3) {
try {
const response = await fetch(url);
return await response.json();
} catch (err) {
if (retries <= 0) throw err;
return fetchWithRetry(url, retries - 1);
}
}
// 使用catch方法
fetchWithRetry('https://api.example.com/data')
.then(data => console.log(data))
.catch(err => console.error('Failed after retries:', err));
并行执行多个异步操作
当需要并行执行多个不依赖的异步操作时,可以使用Promise.all配合await,而不是顺序await每个操作。
async function fetchMultiple() {
const [user, posts] = await Promise.all([
fetch('/user/1').then(r => r.json()),
fetch('/posts?userId=1').then(r => r.json())
]);
return { user, posts };
}
async函数的执行顺序
理解async函数的执行顺序很重要,await会暂停函数执行,但不会阻塞事件循环。
console.log('Start');
async function asyncFunc() {
console.log('Async function start');
await Promise.resolve();
console.log('Async function after await');
}
asyncFunc();
console.log('End');
// 输出顺序:
// Start
// Async function start
// End
// Async function after await
在循环中使用await
在循环中使用await需要注意执行顺序,有时需要并行执行而不是顺序执行。
// 顺序执行
async function processArray(array) {
for (const item of array) {
await processItem(item); // 每个item顺序处理
}
}
// 并行执行
async function processArrayParallel(array) {
await Promise.all(array.map(item => processItem(item)));
}
async函数与Promise的互操作性
async函数返回Promise,因此可以与其他Promise API无缝协作。
async function getUser(id) {
return { id, name: `User ${id}` };
}
// 可以像普通Promise一样使用
getUser(1)
.then(user => console.log(user))
.catch(err => console.error(err));
// 也可以在另一个async函数中使用
async function printUser(id) {
const user = await getUser(id);
console.log(user);
}
常见使用场景
async/await特别适合处理需要顺序执行的异步操作,如API调用链、文件处理等。
async function processOrder(orderId) {
const order = await fetchOrder(orderId);
const user = await fetchUser(order.userId);
const address = await fetchAddress(user.addressId);
await sendConfirmationEmail(user.email, order, address);
return { order, user, address };
}
性能考虑
虽然async/await使代码更易读,但不当使用可能影响性能。避免不必要的await,合理并行化操作。
// 低效写法
async function inefficient() {
const a = await fetchA();
const b = await fetchB(); // 等待fetchA完成才开始
return a + b;
}
// 高效写法
async function efficient() {
const [a, b] = await Promise.all([fetchA(), fetchB()]);
return a + b;
}
在类方法中使用
async函数也可以作为类的方法使用,保持相同的语法和行为。
class ApiClient {
constructor(baseUrl) {
this.baseUrl = baseUrl;
}
async get(resource) {
const response = await fetch(`${this.baseUrl}/${resource}`);
return response.json();
}
}
const client = new ApiClient('https://api.example.com');
client.get('users/1').then(user => console.log(user));
与生成器函数的比较
async/await可以看作是生成器函数的语法糖,但更专注于异步流程控制。
// 使用生成器函数
function* generatorFetch() {
const response = yield fetch('/data');
const data = yield response.json();
return data;
}
// 使用async/await
async function asyncFetch() {
const response = await fetch('/data');
const data = await response.json();
return data;
}
在Node.js中的实际应用
在Node.js中,async/await常用于文件操作、数据库查询等I/O密集型任务。
const fs = require('fs').promises;
async function readFiles() {
try {
const file1 = await fs.readFile('file1.txt', 'utf8');
const file2 = await fs.readFile('file2.txt', 'utf8');
return { file1, file2 };
} catch (err) {
console.error('Error reading files:', err);
throw err;
}
}
与事件发射器的结合使用
可以将事件发射器包装成Promise,以便在async函数中使用。
const { EventEmitter } = require('events');
function eventToPromise(emitter, eventName) {
return new Promise((resolve, reject) => {
emitter.once(eventName, resolve);
emitter.once('error', reject);
});
}
async function waitForEvent() {
const emitter = new EventEmitter();
setTimeout(() => emitter.emit('data', 'Hello'), 1000);
const data = await eventToPromise(emitter, 'data');
console.log(data); // 'Hello'
}
在Express路由中的使用
在Express路由处理函数中使用async/await可以简化错误处理。
const express = require('express');
const app = express();
app.get('/user/:id', async (req, res, next) => {
try {
const user = await getUserById(req.params.id);
if (!user) return res.status(404).send('User not found');
res.json(user);
} catch (err) {
next(err); // 错误会传递给错误处理中间件
}
});
async function getUserById(id) {
// 模拟数据库查询
return new Promise(resolve => {
setTimeout(() => resolve({ id, name: `User ${id}` }), 100);
});
}
与流处理的结合
虽然流处理通常基于事件,但可以创建辅助函数使其与async/await兼容。
const { pipeline } = require('stream/promises');
const fs = require('fs');
async function processFile(inputFile, outputFile) {
try {
await pipeline(
fs.createReadStream(inputFile),
// 这里可以添加转换流
fs.createWriteStream(outputFile)
);
console.log('Pipeline succeeded');
} catch (err) {
console.error('Pipeline failed:', err);
}
}
在测试中的使用
async/await可以简化异步测试代码,使测试用例更清晰。
const assert = require('assert');
const { test } = require('node:test');
test('async test example', async () => {
const result = await someAsyncFunction();
assert.strictEqual(result, expectedValue);
});
async function someAsyncFunction() {
return new Promise(resolve => setTimeout(() => resolve(42), 10));
}
调试async函数
调试async函数与调试同步代码类似,但需要注意调用栈和断点位置。
async function debugExample() {
console.log('Start debugging');
const result = await fetchData(); // 可以在这里设置断点
console.log('Data:', result);
const processed = process(result); // 另一个断点位置
return processed;
}
浏览器兼容性和转译
现代浏览器都支持async/await,但对于旧环境可能需要Babel转译。
// Babel会将其转换为兼容代码
async function oldBrowserSupport() {
const data = await fetchData();
console.log(data);
}
与Web Worker的交互
在Web Worker中使用async/await可以简化消息处理。
// worker.js
self.onmessage = async (event) => {
try {
const result = await heavyComputation(event.data);
self.postMessage({ status: 'success', result });
} catch (err) {
self.postMessage({ status: 'error', error: err.message });
}
};
async function heavyComputation(data) {
// 模拟耗时计算
return new Promise(resolve => {
setTimeout(() => resolve(data * 2), 1000);
});
}
在定时器中的使用
可以将setTimeout等定时器函数Promise化,以便在async函数中使用。
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function timedOperation() {
console.log('Start');
await delay(1000);
console.log('After 1 second');
await delay(500);
console.log('After another 0.5 second');
}
与第三方库的集成
许多现代Node.js库都支持Promise,可以无缝与async/await集成。
const axios = require('axios');
async function fetchFromMultipleAPIs() {
const [github, reddit] = await Promise.all([
axios.get('https://api.github.com/users/octocat'),
axios.get('https://www.reddit.com/r/node.json')
]);
return { github: github.data, reddit: reddit.data };
}
递归async函数
async函数可以递归调用,但要注意堆栈和性能。
async function recursiveFetch(url, depth = 0) {
if (depth > 3) return null;
const response = await fetch(url);
const data = await response.json();
if (data.next) {
return recursiveFetch(data.next, depth + 1);
}
return data;
}
在CLI工具中的应用
Node.js CLI工具中,async/await可以简化用户输入处理等异步操作。
const readline = require('readline').createInterface({
input: process.stdin,
output: process.stdout
});
function questionAsync(prompt) {
return new Promise(resolve => {
readline.question(prompt, resolve);
});
}
async function interactiveCLI() {
const name = await questionAsync('What is your name? ');
const age = await questionAsync('How old are you? ');
console.log(`Hello ${name}, you are ${age} years old`);
readline.close();
}
内存管理注意事项
长时间运行的async函数可能持有不必要的引用,需要注意内存泄漏问题。
async function processLargeData() {
const bigData = await getLargeData(); // 大数据
const result = computeResult(bigData);
// 明确清除不再需要的大数据引用
bigData = null;
return result;
}
与WebSocket的交互
WebSocket通信可以封装为Promise,以便在async函数中使用。
function websocketPromise(ws, message) {
return new Promise((resolve, reject) => {
ws.send(message);
ws.once('message', resolve);
ws.once('error', reject);
ws.once('close', () => reject(new Error('WebSocket closed')));
});
}
async function wsCommunication(ws) {
try {
const response = await websocketPromise(ws, 'ping');
console.log('Received:', response);
} catch (err) {
console.error('WebSocket error:', err);
}
}
在微服务架构中的应用
在微服务间调用时,async/await可以简化跨服务通信代码。
async function placeOrder(userId, items) {
const user = await userService.getUser(userId);
const inventory = await inventoryService.checkItems(items);
const payment = await paymentService.processPayment(user, items);
const order = await orderService.createOrder(user, items, payment);
await notificationService.sendOrderConfirmation(user, order);
return order;
}
与GraphQL解析器的结合
GraphQL解析器非常适合使用async/await,因为经常需要从多个数据源获取数据。
const resolvers = {
Query: {
user: async (_, { id }) => {
const user = await db.users.findById(id);
const posts = await db.posts.findByUserId(id);
return { ...user, posts };
}
},
Mutation: {
createPost: async (_, { input }, context) => {
if (!context.user) throw new Error('Unauthorized');
return db.posts.create({ ...input, authorId: context.user.id });
}
}
};
在Serverless函数中的使用
Serverless函数通常需要处理各种异步操作,async/await是理想选择。
// AWS Lambda示例
exports.handler = async (event) => {
try {
const data = await processEvent(event);
return {
statusCode: 200,
body: JSON.stringify(data)
};
} catch (err) {
return {
statusCode: 500,
body: JSON.stringify({ error: err.message })
};
}
};
async function processEvent(event) {
// 处理事件数据
return { processed: true, timestamp: Date.now() };
}
与TypeScript的类型系统结合
TypeScript为async函数提供了良好的类型支持,可以精确描述返回值类型。
interface User {
id: number;
name: string;
}
async function fetchUser(id: number): Promise<User> {
const response = await fetch(`/users/${id}`);
return response.json();
}
async function printUserName(id: number): Promise<void> {
const user = await fetchUser(id);
console.log(user.name);
}
在中间件管道中的应用
中间件管道可以设计为async函数链,便于处理请求和响应。
async function middlewarePipeline(context, middlewares) {
for (const middleware of middlewares) {
await middleware(context);
if (context.finished) break;
}
return context;
}
// 使用示例
const context = { request: {}, response: {} };
await middlewarePipeline(context, [
async (ctx) => { ctx.user = await getUser(ctx.request); },
async (ctx) => { if (!ctx.user) throw new Error('Unauthorized'); },
async (ctx) => { ctx.response.data = await getData(ctx.user); }
]);
与数据库事务的结合
数据库事务处理是async/await的典型应用场景,需要顺序执行多个操作。
async function transferFunds(senderId, receiverId, amount) {
const connection = await db.getConnection();
try {
await connection.beginTransaction();
await connection.query(
'UPDATE accounts SET balance = balance - ? WHERE id = ?',
[amount, senderId]
);
await connection.query(
'UPDATE accounts SET balance = balance + ? WHERE id = ?',
[amount, receiverId]
);
await connection.commit();
return true;
} catch (err) {
await connection.rollback();
throw err;
} finally {
connection.release();
}
}
处理分页数据
async/await简化了分页数据的获取过程,可以顺序获取所有页面。
async function fetchAllPages(baseUrl) {
let allData = [];
let page = 1;
let hasMore = true;
while (hasMore) {
const response = await fetch(`${baseUrl}?page=${page}`);
const { data, totalPages } = await response.json();
allData = allData.concat(data);
hasMore = page < totalPages;
page++;
}
return allData;
}
实现重试逻辑
async/await使重试逻辑的实现变得简单直观。
async function fetchWithRetry(url, options = {}, retries = 3) {
try {
const response = await fetch(url, options);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return await response.json();
} catch (err) {
if (retries <= 0) throw err;
await new Promise(r => setTimeout(r, 1000 * (4 - retries))); // 指数退避
return fetchWithRetry(url, options, retries - 1);
}
}
与缓存策略的结合
async函数可以方便地实现缓存逻辑,减少不必要的重复请求。
const cache = new Map();
async function getWithCache(key, fetcher) {
if (cache.has(key)) {
return cache.get(key);
}
const data = await fetcher();
cache.set(key, data);
return data;
}
async function getUserWithCache(id) {
return getWithCache(`user:${id}`, () => fetchUser(id));
}
处理并发限制
通过async函数可以实现并发控制,限制同时进行的异步操作数量。
class TaskQueue {
constructor(concurrency) {
this.concurrency = concurrency;
this.running = 0;
this.queue = [];
}
async runTask(task) {
return new Promise((resolve, reject) => {
this.queue.push(() => task().then(resolve, reject));
this.next();
});
}
async next() {
while (this.running < this.concurrency && this.queue.length) {
const task = this.queue.shift();
this.running++;
try {
await task();
} finally {
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:Promise原理与使用
下一篇:错误处理策略