HVRDHVRD
JavaScript

Async JS

JavaScript isn’t just about synchronous code — mastering asynchronous patterns is essential. Below is a complete guide covering Callbacks, Functional Arguments, Callback Hell, and the Event Loop.

Asynchronous JavaScript – The Problem

JavaScript is single-threaded, so blocking operations (e.g., reading files) would freeze the entire app if handled synchronously.

Instead, JavaScript uses asynchronous patterns to handle such tasks without blocking the main thread.


Illustration – File I/O: Sync vs Async

Synchronous File Read (Blocking)

const fs = require('fs');

console.log('Start reading file synchronously');

const data = fs.readFileSync('./large-file.txt', 'utf8');

console.log('File content length:', data.length);
console.log('Finished synchronous read');
  • The process blocks until the file is fully read.
  • Nothing else can execute during the read.
  • Total time depends entirely on file size.

Asynchronous File Read (Non-blocking)

const fs = require('fs');

console.log('Start reading file asynchronously');

fs.readFile('./large-file.txt', 'utf8', (err, data) => {
  if (err) {
    console.error('Error reading file:', err);
    return;
  }
  console.log('File content length:', data.length);
});

console.log('Finished initiating async read');

Expected Output Order:

Start reading file asynchronously  
Finished initiating async read  
File content length: 1234567

Functional Arguments

A functional argument is a function passed into another function as an argument. It allows the receiving function to call it when needed, providing a flexible way to handle asynchronous or deferred execution.

Example

function greet(name, formatter) {
  console.log(formatter(name));
}

function uppercase(name) {
  return name.toUpperCase();
}

greet('Alice', uppercase);  // Outputs: ALICE

Callbacks

A callback is a function passed as an argument to another function to be executed after some asynchronous task completes.

Example Using Functional Argument with fs.readFile

const fs = require('fs');

function readFileAsync(filePath, callback) {
  fs.readFile(filePath, 'utf8', (err, data) => {
    callback(err, data);
  });
}

console.log('Start reading file asynchronously');

readFileAsync('./large-file.txt', (err, data) => {
  if (err) {
    console.error('Error:', err);
    return;
  }
  console.log('File content length:', data.length);
});

console.log('File read initiated');

Callback Hell

When multiple asynchronous tasks depend on each other, using callbacks leads to deeply nested code — a situation called Callback Hell.

const fs = require('fs');

fs.readFile('./file1.txt', 'utf8', (err, data1) => {
  if (err) return console.error('Error reading file1:', err);

  fs.readFile('./file2.txt', 'utf8', (err, data2) => {
    if (err) return console.error('Error reading file2:', err);

    fs.readFile('./file3.txt', 'utf8', (err, data3) => {
      if (err) return console.error('Error reading file3:', err);

      console.log('All files read:', data1.length, data2.length, data3.length);
    });
  });
});

Synchronous File Read with Functional Argument

const fs = require('fs');

function readFileSyncWithCallback(filePath, callback) {
  try {
    const data = fs.readFileSync(filePath, 'utf8');
    callback(null, data);
  } catch (err) {
    callback(err, null);
  }
}

console.log('Start reading file synchronously');

readFileSyncWithCallback('./large-file.txt', (err, data) => {
  if (err) {
    console.error('Error:', err);
  } else {
    console.log('File content length:', data.length);
  }
});

console.log('Finished synchronous read');

Event Loop – How Async JS Works

  1. Call Stack executes synchronous code.
  2. Async APIs (e.g., fs.readFile, setTimeout) run in Web APIs.
  3. Their callback moves to the Task Queue when ready.
  4. Event Loop moves tasks from the queue to the Call Stack.
console.log("Start");

setTimeout(() => {
  console.log("Async Timeout");
}, 0);

console.log("End");

// Output:
// Start  
// End  
// Async Timeout