Async/Await is a powerful and modern approach to handle asynchronous operations in Node.js. Introduced in ECMAScript 2017 (ES8), it simplifies the process of writing asynchronous code and makes it look synchronous, enhancing readability and maintainability. In this document, we will explore the fundamentals of Async/Await in Node.js, its syntax, behavior, practical applications, and best practices.
Callbacks were the traditional way of handling asynchronous operations in Node.js. However, they often led to deeply nested structures known as "callback hell".
const fs = require('fs');
fs.readFile('file.txt', 'utf8', function(err, data) {
if (err) {
return console.error(err);
}
console.log(data);
});
Promises were introduced to solve callback hell by chaining asynchronous operations.
const fs = require('fs').promises;
fs.readFile('file.txt', 'utf8')
.then(data => console.log(data))
.catch(err => console.error(err));
Async/Await further simplifies asynchronous programming by allowing us to write asynchronous code as if it were synchronous.
const fs = require('fs').promises;
async function readFile() {
try {
const data = await fs.readFile('file.txt', 'utf8');
console.log(data);
} catch (err) {
console.error(err);
}
}
readFile();
An async function is a function declared with the async keyword. It always returns a Promise. If the function returns a value, the Promise will be resolved with that value. If the function throws an error, the Promise will be rejected.
async function functionName() {
// Function body
}
async function greet() {
return "Hello, Async!";
}
greet().then(message => console.log(message));
The await keyword can only be used inside async functions. It pauses the execution of the function until the Promise is resolved or rejected.
async function fetchData() {
const result = await Promise.resolve("Data received");
console.log(result);
}
fetchData();
Errors in async functions can be caught using try...catch blocks.
async function fetchData() {
try {
const result = await Promise.reject("Something went wrong");
console.log(result);
} catch (error) {
console.error("Caught error:", error);
}
}
fetchData();
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function chainCalls() {
await delay(1000);
console.log("First call done");
await delay(1000);
console.log("Second call done");
await delay(1000);
console.log("Third call done");
}
chainCalls();
const https = require('https');
function getJSON(url) {
return new Promise((resolve, reject) => {
https.get(url, res => {
let data = '';
res.on('data', chunk => {
data += chunk;
});
res.on('end', () => {
try {
resolve(JSON.parse(data));
} catch (err) {
reject(err);
}
});
}).on('error', reject);
});
}
async function fetchUser() {
try {
const user = await getJSON('https://jsonplaceholder.typicode.com/users/1');
console.log(user);
} catch (err) {
console.error("Fetch failed:", err.message);
}
}
fetchUser();
async function runSequential() {
const a = await Promise.resolve("First");
const b = await Promise.resolve("Second");
const c = await Promise.resolve("Third");
console.log(a, b, c);
}
runSequential();
async function runParallel() {
const [a, b, c] = await Promise.all([
Promise.resolve("First"),
Promise.resolve("Second"),
Promise.resolve("Third")
]);
console.log(a, b, c);
}
runParallel();
You can use async/await inside loops, but be aware that forEach does not support async/await properly.
const list = [1, 2, 3];
list.forEach(async (item) => {
await delay(1000);
console.log(item);
});
async function loopAsync() {
const list = [1, 2, 3];
for (let item of list) {
await delay(1000);
console.log(item);
}
}
loopAsync();
async function getValue() {
return 42;
}
getValue().then(value => console.log("Returned:", value));
const mysql = require('mysql2/promise');
async function connectDb() {
try {
const connection = await mysql.createConnection({
host: 'localhost',
user: 'root',
database: 'test'
});
const [rows] = await connection.execute('SELECT * FROM users');
console.log(rows);
} catch (err) {
console.error("DB Error:", err.message);
}
}
connectDb();
async function runTasks() {
try {
const result1 = await Promise.reject("Error 1");
const result2 = await Promise.reject("Error 2");
} catch (err) {
console.error("Caught error:", err);
}
}
runTasks();
async function outer() {
async function inner() {
return "Inner done";
}
const result = await inner();
console.log(result);
}
outer();
class Custom {
then(resolve, reject) {
setTimeout(() => resolve("Resolved custom object"), 1000);
}
}
async function useCustom() {
const result = await new Custom();
console.log(result);
}
useCustom();
| Aspect | Promises | Async/Await |
|---|---|---|
| Readability | Moderate | High |
| Error Handling | .catch() | try/catch |
| Syntax | Chained | Flat & sequential |
| Debugging | Stack traces harder to follow | More intuitive stack trace |
With the introduction of async iterators, top-level await (in ES2022), and more robust debugging tools, asynchronous programming in Node.js continues to evolve. Async/await has become the default and preferred way to handle asynchronous operations in modern Node.js applications.
Async/Await revolutionized asynchronous programming in JavaScript and Node.js. It provides a clean, concise, and readable way to write asynchronous code, minimizing complexity and errors often found in callback- or promise-based code. Async functions return Promises by default and integrate seamlessly with modern JavaScript features.
Whether you're accessing APIs, performing file or database operations, or simply waiting for timers, async/await can streamline your code, make it easier to follow, and reduce the chances of introducing bugs. Understanding how to use it properlyβand when not to use itβis crucial for writing effective, scalable Node.js applications.
A function passed as an argument and executed later.
Runs multiple instances to utilize multi-core systems.
Reusable blocks of code, exported and imported using require() or import.
nextTick() executes before setImmediate() in the event loop.
Starts a server and listens on specified port.
Node Package Manager β installs, manages, and shares JavaScript packages.
A minimal and flexible web application framework for Node.js.
A stream handles reading or writing data continuously.
It processes asynchronous callbacks and non-blocking I/O operations efficiently.
Node.js is a JavaScript runtime built on Chrome's V8 engine for server-side scripting.
An object representing the eventual completion or failure of an asynchronous operation.
require is CommonJS; import is ES6 syntax (requires transpilation or newer versions).
Use module.exports or exports.functionName.
Variables stored outside the code for configuration, accessed using process.env.
MongoDB, often used with Mongoose for schema management.
Describes project details and manages dependencies and scripts.
Synchronous blocks execution; asynchronous runs in background without blocking.
Allows or restricts resources shared between different origins.
Use try-catch, error events, or middleware for error handling.
Provides file system-related operations like read, write, delete.
Using event-driven architecture and non-blocking I/O.
Functions in Express that execute during request-response cycle.
A set of routes or endpoints to interact with server logic or databases.
Yes, it's single-threaded but handles concurrency using the event loop and asynchronous callbacks.
Middleware to parse incoming request bodies, like JSON or form data.
Copyrights © 2024 letsupdateskills All rights reserved