DEV Community

Ahmed Rakan
Ahmed Rakan

Posted on

5 Lesser-Known Asynchronous Patterns in Node.js (With Examples)

Async Iterator/Generator Pattern

When fetching paginated data from an API, an Async Generator can fetch each page lazily, allowing better control over memory and network usage.

async function* fetchPaginatedData(totalPages) {
  for (let page = 1; page <= totalPages; page++) {
    await new Promise(resolve => setTimeout(resolve, 1000)); // Simulating delay
    yield `Data from page ${page}`;
  }
}

(async () => {
  for await (const data of fetchPaginatedData(3)) {
    console.log(data);  // Logs each page's data with a delay
  }
})();
Enter fullscreen mode Exit fullscreen mode

Event Loop - Execution Order

The Event Loop in Node.js ensures non-blocking behavior in a single-threaded environment by handling tasks in phases. Understanding the order of execution helps you predict behavior.

console.log("Start");

setImmediate(() => console.log("📢 setImmediate"));
setTimeout(() => console.log("⏳ setTimeout"), 0);
Promise.resolve().then(() => console.log("✅ Promise"));
process.nextTick(() => console.log("⚡ nextTick"));

console.log("End");
Enter fullscreen mode Exit fullscreen mode

Output:

Start  
End  
⚡ nextTick  
✅ Promise  
⏳ setTimeout  
📢 setImmediate
Enter fullscreen mode Exit fullscreen mode

Phases:

  1. nextTick: Executes right after the current operation.
  2. Promise: Executes after nextTick, before timers.
  3. setTimeout: Executes after timers phase.
  4. setImmediate: Executes in the check phase, after timers.

Semaphore Pattern in Node.js

A Semaphore limits the number of concurrent tasks, useful for controlling access to shared resources. It allows you to limit concurrent execution.

class Semaphore {
  constructor(max) {
    this.max = max;
    this.count = 0;
    this.waiting = [];
  }

  async acquire() {
    if (this.count < this.max) {
      this.count++;
      return;
    }
    await new Promise(resolve => this.waiting.push(resolve));
  }

  release() {
    this.count--;
    const next = this.waiting.shift();
    if (next) next();
  }
}

const sem = new Semaphore(2);

async function limitedTask(id) {
  await sem.acquire();
  console.log(`Task ${id} started`);
  await new Promise(r => setTimeout(r, 1000));  // Simulate work
  sem.release();
}

[1, 2, 3, 4].forEach(limitedTask);
Enter fullscreen mode Exit fullscreen mode

Barrier Pattern in Node.js

The Barrier pattern synchronizes multiple tasks, ensuring they all proceed at the same time once all tasks reach the barrier.

class Barrier {
  constructor(count) {
    this.count = count;
    this.promises = [];
  }

  async wait() {
    const promise = new Promise(resolve => this.promises.push(resolve));
    if (--this.count === 0) {
      this.promises.forEach(resolve => resolve());
    }
    return promise;
  }
}

const barrier = new Barrier(3);

async function worker() {
  console.log('Waiting at barrier');
  await barrier.wait();
  console.log('Proceeding');
}

worker();  // Task 1
worker();  // Task 2
worker();  // Task 3
Enter fullscreen mode Exit fullscreen mode

AbortController Pattern

The AbortController allows aborting asynchronous tasks. It can be used to cancel long-running operations like fetch requests.

const controller = new AbortController();
const signal = controller.signal;

fetch('https://api.example.com/data', { signal })
  .then(response => response.json())
  .catch(error => console.log('Fetch aborted', error));

controller.abort();  // Aborts the fetch
Enter fullscreen mode Exit fullscreen mode

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.