NNODE LOOP LABruntime observatoryNNEON · Articles in 90 languages
CONNECTING
v24.18.0linux/x64
Many sources → one thread
02

Event demultiplexer

Start a timer, file read, and DNS lookup together, then observe ready callbacks returning to JavaScript.

PROCESS IDcurrent server
UPTIMEsince startup
LOOP DELAY P95perf_hooks
UTILIZATIONevent loop
HTTP ROUNDTRIPbrowser → server
LIVE TRACE

Timeline

READY
0 ms
Events will appear hereRun the selected scenario
#TIMESOURCEEVENT
Waiting for an experiment…
CHAPTER 02
DEEP DIVE · FROM BASICS TO CODE

Understanding: Event demultiplexer

You can read this chapter before running the experiment. Then return to the live trace and match each concept to a real event.

FIRST, IN PLAIN LANGUAGE

Think of a hotel dispatcher. They do not stand at every door and repeatedly ask whether a room is ready. Services report readiness, and the dispatcher forwards those notifications to one receptionist: the JavaScript thread.

TECHNICAL FOUNDATION

Node registers async work with libuv and returns control to the application. libuv uses OS readiness for sockets, usually delegates regular files and dns.lookup to its native thread pool, and tracks timers as time thresholds. These internal paths converge when callbacks return to JavaScript.

Why it mattersThis model lets one process handle many connections without creating one JavaScript thread per request.
WHERE THE WORK RUNS
01YOUR JS CODEfunctions · callbacks
02NODE APIsfs · crypto · timers
03V8 + LIBUVheap · loop · pool
04OPERATING SYSTEMI/O · threads · memory
01 · GLOSSARY

Terms used in this experiment

Understand the words first, then the execution order.

01

Event source

A readiness source such as a socket, timer, file operation, DNS request, or worker message.

02

Demultiplexer

A mechanism that waits for many sources and returns the subset that is ready.

03

libuv

The native Node library that unifies the Event Loop and async operations across operating systems.

04

Poll

The phase that handles many ready I/O callbacks and may wait for additional events.

02 · MECHANICS

What happens step by step

Each step maps to an observable runtime state.

  1. 01
    Registration

    JavaScript starts a timer, readFile, and DNS without waiting in place.

  2. 02
    Delegation

    libuv uses a timer structure, an OS facility, or its native thread pool.

  3. 03
    Free stack

    Node can process other requests while the sources are not ready.

  4. 04
    Readiness signal

    The OS/libuv reports which source has completed.

  5. 05
    Callback

    The ready function enters the JavaScript stack when it is free.

03 · CONTEXT

Where the result needs context

These details explain why similar code can sometimes produce a different trace.

01

Not every source follows one path

A network socket usually uses epoll, kqueue, or IOCP; a regular file often uses the thread pool; a timer is a time-threshold check. “Demultiplexer” is a useful shared model, not one universal object behind every API.

02

dns.lookup differs from dns.resolve

dns.lookup uses the system resolver and usually the shared libuv pool. dns.resolve* methods issue DNS requests through another path. “DNS” alone does not identify the mechanism.

03

Promise.all starts nothing

readFile, lookup, and the timer start while the expressions above are evaluated. Promise.all receives already-created Promises, preserves result order, and coordinates waiting.

04

Fast rejection is not cancellation

On the success path Promise.all waits for all three results. If one Promise rejects, the aggregate rejects early, but the remaining operations are not automatically canceled.

01
Theory

First understand which parts of Node participate in execution.

02
Simplified code

Then remove instrumentation and focus on the central mechanism.

03
Runtime code

Finally match the model to the code that produces the live trace.

04 · Simplified code

A minimal model without instrumentation

src/demos.js · educational snippetJavaScript
const file = fs.promises.readFile('package.json');
const dns = dns.promises.lookup('localhost');
const timer = new Promise(r => setTimeout(r, 180));

await Promise.all([file, dns, timer]);
05 · Runtime code

The complete code executed by the scenario

This is not an alternative example: these are the functions and files used by the Run button.

ACTUAL SOURCE

The source is generated from the real server function. Scenarios using a child process or Worker include every participating file.

src/demos.js
scenario56 lines
import { readFile } from 'node:fs';
import { lookup } from 'node:dns';
import { fileURLToPath } from 'node:url';

const packageJsonPath = fileURLToPath(
  new URL('../package.json', import.meta.url),
);

async function eventDemultiplexer(emit) {
  emit('call-stack', 'sync', 'Регистрируем три независимые операции');

  const timerTask = new Promise((resolve) => {
    emit('timers', 'schedule', 'Таймер 180 мс передан подсистеме таймеров');
    setTimeout(() => {
      emit('timers', 'callback', 'Таймер готов: callback вернулся в JavaScript');
      resolve('timer');
    }, 180);
  });

  const fileTask = new Promise((resolve, reject) => {
    emit('libuv', 'schedule', 'Чтение package.json делегировано libuv');
    readFile(packageJsonPath, 'utf8', (error, content) => {
      if (error) {
        reject(error);
        return;
      }
      emit(
        'poll',
        'callback',
        `Файл готов: получено ${Buffer.byteLength(content)} байт`,
      );
      resolve('file');
    });
  });

  const dnsTask = new Promise((resolve, reject) => {
    emit('libuv', 'schedule', 'DNS lookup localhost делегирован libuv');
    lookup('localhost', (error, address) => {
      if (error) {
        reject(error);
        return;
      }
      emit('poll', 'callback', `DNS готов: localhost → ${address}`);
      resolve('dns');
    });
  });

  emit(
    'demultiplexer',
    'info',
    'JS-стек свободен; Event Loop ожидает сигналы готовности, а не опрашивает каждую функцию вручную',
  );

  const completed = await Promise.all([timerTask, fileTask, dnsTask]);
  emit('result', 'result', `Все источники готовы: ${completed.join(', ')}`);
}

The emit(...) calls become live-trace rows. await and Promise keep the HTTP stream open until the scenario completes.

06 · DO NOT CONFUSE

Common misconceptions

The myth is on the left; the accurate model is on the right.

MYTH

Node repeatedly calls every function to ask whether it is ready.

ACTUALLY

Waiting is performed by efficient OS and libuv mechanisms.

MYTH

Every async operation creates a new thread.

ACTUALLY

Sockets usually use OS readiness; only some APIs use a limited thread pool.

MYTH

Parallel completion means parallel JavaScript callbacks.

ACTUALLY

Callbacks still enter a single JavaScript environment one at a time.

MYTH

Promise.all starts and cancels its operations.

ACTUALLY

The operations are normally already running. Cancellation needs a separate mechanism such as AbortSignal when the specific API supports it.

07 · SELF-CHECK

Explain it in your own words

If you can explain the answer without quoting documentation, your mental model is starting to take shape.

  1. Why does a fast DNS lookup not have to wait for a slow timer?
  2. Which part differs between a network socket and a regular file?