What is Asynchronous Processing
Asynchronous processing is a mechanism that allows other tasks to continue without waiting for time-consuming operations (file reading, API calls, timers, etc.) to complete.
flowchart LR
subgraph Sync["Synchronous (Wait in sequence)"]
direction LR
T1["Task1"] --> C1["Complete"] --> T2["Task2"] --> C2["Complete"] --> T3["Task3"] --> C3["Complete"]
end
flowchart TB
subgraph Async["Asynchronous (Execute concurrently)"]
direction TB
S1["Task1 Start"] --> S2["Task2 Start"] --> S3["Task3 Start"]
S3 --> E1["Task1 Complete"]
E1 --> E2["Task2 Complete"]
E2 --> E3["Task3 Complete"]
end
Event Loop
JavaScript is single-threaded but achieves asynchronous processing through the event loop.
flowchart TB
CS["Call Stack<br/>(Executes synchronous code)"]
EL["Event Loop<br/>(Pulls from queue when call stack is empty)"]
MicroQ["Microtask Queue<br/>(Promise, queueMicrotask)<br/>★ Priority: High"]
MacroQ["Macrotask Queue<br/>(setTimeout, I/O)<br/>Priority: Low"]
CS <--> EL
EL --> MicroQ
EL --> MacroQ
Execution Order Example
console.log('1'); // Synchronous
setTimeout(() => console.log('2'), 0); // Macrotask
Promise.resolve().then(() => console.log('3')); // Microtask
console.log('4'); // Synchronous
// Output: 1, 4, 3, 2
Callbacks
The most basic asynchronous pattern.
// Callback hell example
fs.readFile('file1.txt', (err, data1) => {
if (err) throw err;
fs.readFile('file2.txt', (err, data2) => {
if (err) throw err;
fs.readFile('file3.txt', (err, data3) => {
if (err) throw err;
console.log(data1, data2, data3);
});
});
});
Problems:
- Deep nesting (callback hell)
- Complex error handling
- Poor readability
Promise
An object representing the eventual completion (or failure) of an asynchronous operation.
// Creating a Promise
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
const success = true;
if (success) {
resolve('Success!');
} else {
reject(new Error('Failed'));
}
}, 1000);
});
// Using a Promise
myPromise
.then(result => console.log(result))
.catch(error => console.error(error))
.finally(() => console.log('Complete'));
Promise Chaining
fetchUser(userId)
.then(user => fetchPosts(user.id))
.then(posts => fetchComments(posts[0].id))
.then(comments => {
console.log(comments);
})
.catch(error => {
console.error('Error:', error);
});
Promise.all / Promise.race
// Promise.all: Wait for all to complete
const results = await Promise.all([
fetchUser(1),
fetchUser(2),
fetchUser(3)
]);
// → [user1, user2, user3]
// Promise.race: Return the first to complete
const fastest = await Promise.race([
fetchFromServer1(),
fetchFromServer2()
]);
// Promise.allSettled: Get all results (including failures)
const results = await Promise.allSettled([
fetchUser(1),
fetchUser(999) // Non-existent user
]);
// → [{status: 'fulfilled', value: user1}, {status: 'rejected', reason: Error}]
async/await
Syntactic sugar for writing Promises more intuitively.
// async function
async function fetchUserData(userId) {
try {
const user = await fetchUser(userId);
const posts = await fetchPosts(user.id);
const comments = await fetchComments(posts[0].id);
return { user, posts, comments };
} catch (error) {
console.error('Error:', error);
throw error;
}
}
Parallel Execution
// Sequential execution (slow)
async function sequential() {
const user1 = await fetchUser(1); // 1 second
const user2 = await fetchUser(2); // 1 second
const user3 = await fetchUser(3); // 1 second
// Total: 3 seconds
}
// Parallel execution (fast)
async function parallel() {
const [user1, user2, user3] = await Promise.all([
fetchUser(1),
fetchUser(2),
fetchUser(3)
]);
// Total: ~1 second
}
Error Handling
try/catch
async function fetchData() {
try {
const data = await riskyOperation();
return data;
} catch (error) {
if (error.code === 'NOT_FOUND') {
return null;
}
throw error; // Re-throw
}
}
Error Wrapper
// Pattern that returns error as array
async function safeAsync(promise) {
try {
const data = await promise;
return [null, data];
} catch (error) {
return [error, null];
}
}
// Usage
const [error, user] = await safeAsync(fetchUser(id));
if (error) {
console.error('Failed to fetch user:', error);
return;
}
console.log(user);
Concurrency vs Parallelism
flowchart TB
subgraph Concurrent["Concurrent: Multiple tasks overlap in time (Achievable with single thread)"]
direction LR
CA1["Task A"] -.-> CB1["Task B"] -.-> CA2["Task A"] -.-> CB2["Task B"]
end
subgraph Parallel["Parallel: Multiple tasks execute simultaneously (Requires multi-thread/multi-core)"]
direction LR
PA["Task A ─────────────────"]
PB["Task B ─────────────────"]
end
Parallel Processing in Node.js
// Worker Threads
const { Worker } = require('worker_threads');
function runCPUIntensiveTask(data) {
return new Promise((resolve, reject) => {
const worker = new Worker('./heavy-task.js', {
workerData: data
});
worker.on('message', resolve);
worker.on('error', reject);
});
}
Asynchronous Iteration
// for await...of
async function* generateUsers() {
for (let id = 1; id <= 3; id++) {
yield await fetchUser(id);
}
}
for await (const user of generateUsers()) {
console.log(user);
}
// AsyncIterator
const stream = fs.createReadStream('large-file.txt');
for await (const chunk of stream) {
console.log(chunk);
}
Anti-Patterns
Overusing await
// Bad example: Unnecessary await
async function bad() {
return await fetchData(); // await is unnecessary
}
// Good example
async function good() {
return fetchData(); // Return Promise directly
}
Sequential Execution Trap
// Bad example: Sequentially executing independent operations
const user = await fetchUser(id);
const config = await fetchConfig(); // Does not depend on user
// Good example: Parallel execution
const [user, config] = await Promise.all([
fetchUser(id),
fetchConfig()
]);
Summary
Asynchronous programming is essential for modern JavaScript development. The evolution from callbacks to Promises to async/await has made code more readable and maintainable. By understanding the event loop mechanism and implementing proper error handling and parallel execution, you can achieve efficient asynchronous processing.
← Back to list