The async/await syntactic sugar
The Essence of async/await
async/await is syntactic sugar for Generator functions, implemented based on Promises under the hood. It makes asynchronous code look like synchronous code, but the actual execution remains asynchronous. An async function returns a Promise object, and the expression following the await
command is typically a Promise object.
async function fetchData() {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return data;
}
Basic Usage of async Functions
Adding the async
keyword before a function declaration turns it into an async function. Inside an async function, you can use await
expressions, which pause the execution of the async function until the Promise is resolved before continuing.
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);
}
}
Behavior of await Expressions
The await
expression pauses the execution of the async function and waits for the Promise to resolve. If the awaited value is not a Promise, it is returned directly. await
can only be used inside async functions.
async function process() {
const value1 = await 42; // Non-Promise values are returned directly
const value2 = await Promise.resolve('hello');
console.log(value1, value2); // 42 "hello"
}
Error Handling Mechanism
Inside async functions, you can use try/catch
to handle errors or use the Promise's catch
method. Uncaught errors will cause the returned Promise to be rejected.
// Using 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);
}
}
// Using the catch method
fetchWithRetry('https://api.example.com/data')
.then(data => console.log(data))
.catch(err => console.error('Failed after retries:', err));
Parallel Execution of Multiple Asynchronous Operations
When multiple independent asynchronous operations need to be executed in parallel, use Promise.all
with await
instead of sequentially awaiting each operation.
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 };
}
Execution Order of async Functions
Understanding the execution order of async functions is important. await
pauses function execution but does not block the event loop.
console.log('Start');
async function asyncFunc() {
console.log('Async function start');
await Promise.resolve();
console.log('Async function after await');
}
asyncFunc();
console.log('End');
// Output order:
// Start
// Async function start
// End
// Async function after await
Using await in Loops
When using await
in loops, pay attention to the execution order. Sometimes parallel execution is needed instead of sequential execution.
// Sequential execution
async function processArray(array) {
for (const item of array) {
await processItem(item); // Each item is processed sequentially
}
}
// Parallel execution
async function processArrayParallel(array) {
await Promise.all(array.map(item => processItem(item)));
}
Interoperability Between async Functions and Promises
Async functions return Promises, so they can seamlessly work with other Promise APIs.
async function getUser(id) {
return { id, name: `User ${id}` };
}
// Can be used like a regular Promise
getUser(1)
.then(user => console.log(user))
.catch(err => console.error(err));
// Can also be used inside another async function
async function printUser(id) {
const user = await getUser(id);
console.log(user);
}
Common Use Cases
async/await is particularly suitable for handling asynchronous operations that need to be executed sequentially, such as API call chains and file processing.
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 };
}
Performance Considerations
Although async/await makes code more readable, improper use can impact performance. Avoid unnecessary await
and parallelize operations appropriately.
// Inefficient approach
async function inefficient() {
const a = await fetchA();
const b = await fetchB(); // Waits for fetchA to complete before starting
return a + b;
}
// Efficient approach
async function efficient() {
const [a, b] = await Promise.all([fetchA(), fetchB()]);
return a + b;
}
Usage in Class Methods
Async functions can also be used as class methods, maintaining the same syntax and behavior.
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));
Comparison with Generator Functions
async/await can be seen as syntactic sugar for Generator functions but is more focused on asynchronous flow control.
// Using Generator functions
function* generatorFetch() {
const response = yield fetch('/data');
const data = yield response.json();
return data;
}
// Using async/await
async function asyncFetch() {
const response = await fetch('/data');
const data = await response.json();
return data;
}
Practical Applications in Node.js
In Node.js, async/await is commonly used for I/O-intensive tasks like file operations and database queries.
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;
}
}
Integration with Event Emitters
Event emitters can be wrapped into Promises for use in async functions.
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'
}
Usage in Express Routes
Using async/await in Express route handlers simplifies error handling.
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); // Errors are passed to the error-handling middleware
}
});
async function getUserById(id) {
// Simulate a database query
return new Promise(resolve => {
setTimeout(() => resolve({ id, name: `User ${id}` }), 100);
});
}
Integration with Stream Processing
Although stream processing is typically event-based, helper functions can be created to make it compatible with async/await.
const { pipeline } = require('stream/promises');
const fs = require('fs');
async function processFile(inputFile, outputFile) {
try {
await pipeline(
fs.createReadStream(inputFile),
// Transformation streams can be added here
fs.createWriteStream(outputFile)
);
console.log('Pipeline succeeded');
} catch (err) {
console.error('Pipeline failed:', err);
}
}
Usage in Testing
async/await simplifies asynchronous test code, making test cases clearer.
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));
}
Debugging async Functions
Debugging async functions is similar to debugging synchronous code, but pay attention to the call stack and breakpoint locations.
async function debugExample() {
console.log('Start debugging');
const result = await fetchData(); // A breakpoint can be set here
console.log('Data:', result);
const processed = process(result); // Another breakpoint location
return processed;
}
Browser Compatibility and Transpilation
Modern browsers support async/await, but older environments may require Babel transpilation.
// Babel will transpile this into compatible code
async function oldBrowserSupport() {
const data = await fetchData();
console.log(data);
}
Interaction with Web Workers
Using async/await in Web Workers simplifies message handling.
// 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) {
// Simulate heavy computation
return new Promise(resolve => {
setTimeout(() => resolve(data * 2), 1000);
});
}
Usage in Timers
Timer functions like setTimeout
can be Promisified for use in async functions.
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');
}
Integration with Third-Party Libraries
Many modern Node.js libraries support Promises and can seamlessly integrate with 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 };
}
Recursive async Functions
Async functions can call themselves recursively, but be mindful of stack usage and performance.
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;
}
Applications in CLI Tools
In Node.js CLI tools, async/await simplifies asynchronous operations like user input handling.
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();
}
Memory Management Considerations
Long-running async functions may hold unnecessary references, so be mindful of memory leaks.
async function processLargeData() {
const bigData = await getLargeData(); // Large dataset
const result = computeResult(bigData);
// Explicitly clear references to large data that's no longer needed
bigData = null;
return result;
}
Interaction with WebSockets
WebSocket communication can be wrapped in Promises for use in async functions.
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);
}
}
Applications in Microservice Architectures
When making calls between microservices, async/await simplifies cross-service communication code.
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;
}
Integration with GraphQL Resolvers
GraphQL resolvers are well-suited for async/await as they often need to fetch data from multiple sources.
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 });
}
}
};
Usage in Serverless Functions
Serverless functions often need to handle various asynchronous operations, making async/await an ideal choice.
// AWS Lambda example
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) {
// Process event data
return { processed: true, timestamp: Date.now() };
}
Integration with TypeScript's Type System
TypeScript provides excellent type support for async functions, allowing precise description of return types.
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);
}
Applications in Middleware Pipelines
Middleware pipelines can be designed as chains of async functions for easy request and response handling.
async function middlewarePipeline(context, middlewares) {
for (const middleware of middlewares) {
await middleware(context);
if (context.finished) break;
}
return context;
}
// Usage example
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); }
]);
Integration with Database Transactions
Database transaction processing is a classic use case for async/await, requiring sequential execution of multiple operations.
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();
}
}
Handling Paginated Data
async/await simplifies the process of fetching paginated data, allowing sequential retrieval of all pages.
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;
}
Implementing Retry Logic
async/await makes implementing retry logic simple and intuitive.
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))); // Exponential backoff
return fetchWithRetry(url, options, retries - 1);
}
}
Integration with Caching Strategies
Async functions can easily implement caching logic to reduce unnecessary duplicate requests.
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));
}
Handling Concurrency Limits
Concurrency control can be implemented via async functions to limit the number of simultaneous asynchronous operations.
class TaskQueue {
constructor(concurrency) {
this.concurrency = concurrency;
this.running = 0;
this.queue = [];
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:Promise原理与使用
下一篇:错误处理策略