>_
EngineeringNotes
← Back to Node.js & Runtimes
Module 03

Asynchronous Patterns

From callbacks to async/await. Understand the evolution and best practices for handling asynchronous workflows in Node.js.

01

Why Asynchronous Code?

Node.js is inherently non-blocking, which means operations such as file reading, API requests, and database queries do not execute instantly.

The Challenge

How do we reliably handle results that resolve at a later, unpredictable time? The solution lies in applying the correct asynchronous patterns to your application logic.

02

Callbacks (The Old Way)

Definition: A callback is a function passed as an argument to be executed once an asynchronous operation completes.

callback-example.js
javascript
import fs from 'fs';

fs.readFile('file.txt', (err, data) => {
    if (err) throw err;
    console.log(data.toString());
});

The Problem: Callback Hell

javascript
javascript
fs.readFile('a.txt', (err, data1) => {
    fs.readFile('b.txt', (err, data2) => {
        fs.readFile('c.txt', (err, data3) => {
            console.log("Done");
        });
    });
});
  • Code structure becomes difficult to read
  • Tracing variable scopes becomes confusing
  • Deeply nested structures hinder scalability and maintenance
03

Promises (Better Approach)

Definition: A Promise is an object that represents the eventual completion (or failure) of an asynchronous operation and its resulting value.

PendingResolvedRejected
promise-example.js
javascript
import fs from 'fs/promises';

fs.readFile('file.txt')
  .then(data => console.log(data.toString()))
  .catch(err => console.error(err));

Key Benefits

  • — Cleaner syntactic logic than raw callbacks
  • — Prevents deep nesting complications
  • — Easily chainable for sequence operations
04

Async/Await (Modern Standard)

Definition: Syntactic wrapper built on top of Promises. It allows asynchronous code structures to be written and read in a pseudo-synchronous manner.

async-await-example.js
javascript
import fs from 'fs/promises';

async function readFileData() {
    try {
        const data = await fs.readFile('file.txt');
        console.log(data.toString());
    } catch (err) {
        console.error(err);
    }
}

Why it's preferred

  • — Exceptionally clean operational flow
  • — Simplifies debugging natively through try/catch blocks
  • — Highest human readability among asynchronous strategies
05

Error Handling (Critical Application Rules)

Correct Implementation

javascript
javascript
try {
    const data = await fs.readFile('file.txt');
} catch (err) {
    console.error(err);
}

Common Anti-Pattern

javascript
javascript
// No error handling structure
await fs.readFile('file.txt');
Rule of Thumb: Always wrap await calls inside a try-catch block to prevent fatal unhandled promise rejections.
06

Execution Flow Strategy

Sequential (Slower)

javascript
javascript
await task1();
await task2();
await task3();

Each await blocks the previous

Parallel (Faster)

javascript
javascript
await Promise.all([
    task1(), 
    task2(), 
    task3()
]);

Operations trigger concurrently

Always utilize parallel execution when tasks are independent of each other's return state.

07

Core Promise APIs

1. Promise.all()

javascript
javascript
await Promise.all([p1, p2, p3]);
  • Executes all promises natively in parallel.
  • Throws immediately if any individual promise is rejected.

2. Promise.allSettled()

javascript
javascript
await Promise.allSettled([p1, p2, p3]);
  • Waits cleanly for all promises to finish regardless of success or failure.
  • Returns a detailed array containing both successful and failed metadata attributes.

3. Promise.race()

javascript
javascript
await Promise.race([p1, p2, p3]);
  • Returns merely the result of the absolute first promise that completes processing.
08

Node.js Overrided Timers

setTimeout

Schedules execution logic after a specified delay limit.

javascript
javascript
setTimeout(() => {
    console.log("Later");
}, 1000);

setInterval

Continuous repeating execution at bound repeating intervals.

javascript
javascript
setInterval(() => {
    console.log("Repeat");
}, 1000);

setImmediate

Runs identically directly after the current I/O cycles finish.

javascript
javascript
setImmediate(() => {
    console.log("After I/O");
});

Primary Application Use Cases: Scheduling background metrics, manual rate boundaries, handling persistent connection timeouts.

09

process.nextTick() Strategy

javascript
javascript
process.nextTick(() => {
    console.log("Priority Tick Executed");
});

Execution Priority Ranking

Callbacks assigned effectively execute strictly prior to:

  • 1. Unresolved Promise chains
  • 2. Standard Timer tasks
  • 3. Event Loop macro phases

Architectural Warning

Over-utilizing nextTick instances critically forces the engine block, indefinitely delaying transition to any standard event loop processing requests.

10

Introduction to Data Streams

Continuous data streams natively resolve system memory ceilings by resolving structural datasets individually via smaller chunks rather than enforcing large RAM loading constraints globally.

stream-architecture.js
javascript
import fs from 'fs';

const stream = fs.createReadStream('large_dataset.txt');

stream.on('data', chunk => {
    console.log("Received data sector:", chunk.length);
});

Why Utilize Them?

  • Dramatically optimizes memory footprint on servers under variable pressure
  • Secure integration pathways for reliable continuous upload streaming
  • Preferred engine architecture serving video delivery
11

Core Interview Assessment

What defines a Promise object?
Answer:A reference instance managing the eventual temporal completion framework (and tracking resolving or failing variants) of an overarching asynchronous task procedure.
Characterize Sequential versus Parallel design methodology.
Answer:Sequential enforces chronological processing chains; Parallel pipelines commands concurrently relying upon architectural scaling mechanics.
Explain the role of process.nextTick systematically.
Answer:A designated method forcing logic invocation instantly bypassing normal phase polling systems typically leveraged prior to broader loop progression scopes.
Differentiate setImmediate from general setTimeout usage.
Answer:Timeout delegates instructions tracking abstract delay bounds whereas Immediate explicitly triggers post I/O resolution operations intrinsically.
Comparing Asynchronous Models
Syntax ParadigmIdentified DrawbackPrimary Advantage
Callback FunctionInefficient nested architecturesFundamental simplistic integration
Promise ReturnVerbosely stacked logic mappingAdvanced procedural chaining execution
Async / AwaitStandard try/catch integration mandatesExceptional maintainable script logic
API Variant Handling Mechanics
Operational TargetPromise.allPromise.allSettled
Fatal ResolutionHard termination failure constraintsTolerant pipeline tracking execution
Logic GuaranteeRestricted strict success barriersComprehensive status tracking allowances

Core Conceptual Mapping

Legacy RuntimeCallback Structures
Modernization LayerChaining Promises
Optimum ScaleAsynchronous Awaits
  • > Independent procedure workflows universally require horizontal non-blocking sequence parsing (Parallel execution implementation).
  • > Operational runtime processes require resilient explicit boundary catching (Standard Error logic integration).