Is Node.js single thread or not?
setTimeout() or setInterval().
The delay is a threshold, not a guarantee — OS scheduling or a busy
Poll phase can push it later.
ECONNREFUSED.
Rarely visible in application code.
setImmediate() callbacks. Executes immediately after
Poll within the same cycle, making it reliably earlier than
setTimeout(fn, 0) when called from inside an I/O callback.
close events for sockets and handles destroyed abruptly.
Runs last so all pending I/O for that resource has already been processed
before cleanup code runs.
import http from 'node:http'; import { writeFile } from 'node:fs/promises'; import db from './db.js'; const server = http.createServer((req, res) => { if (req.method === 'POST' && req.url === '/request') { // ① Synchronous — executes immediately console.log('start'); // ② Schedules for the Check phase setImmediate(() => { console.log('setImmediate'); }); // ③ Async DB query (callback style to continue to step 4) db.query('SELECT * FROM table', (err, rows) => { if (err) { res.writeHead(500); return res.end('Database Error'); } res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(rows)); }); // ④ Timer phase setTimeout(async () => { const response = await fetch('https://api.example.com/data'); const json = await response.json(); await writeFile('log.txt', JSON.stringify(json, null, 2)); console.log('log.txt saved successfully'); }, 0); } }); server.listen(3000, () => { console.log('Server listening on http://localhost:3000'); });
"start"async function uses await for fetch, JSON parse, and writeFile (all Promise-based) · handler returnsconsole.log('setImmediate') printed · same cycle as the request, right after Poll · fires before the setTimeout(0) which belongs to the next cycle's Timers phasefetch(). Poll runs next and handles the DB result — the HTTP response is sent to the client.await · callback returns control to the loopres.writeHead(500) · on success: res.writeHead(200) + res.end(JSON.stringify(rows)) · HTTP response sent to client ✓api.example.com has responded. Other incoming requests are processed normally during this time.response object now available ·
response.json() called → returns a new pending Promise · suspends at next await
json data available ·
writeFile('log.txt', JSON.stringify(json, null, 2)) called from node:fs/promises →
Promise-based I/O delegated to libuv · async function suspends at await writeFile()
writeFile is from node:fs/promises, completion resolves its Promise. The await writeFile() continuation runs as a microtask — console.log('log.txt saved successfully') executes before the next I/O callback. The full lifecycle of the POST /request is now complete.await writeFile() continuation queued as a microtask
console.log('log.txt saved successfully') printed ·
full operation complete ✓
setImmediate is
always first — it targets Check in the current cycle, while
setTimeout(0) must wait for the Timers phase of the next cycle.
db.query() is handed to libuv instantly. The handler registers the
timer, queues setImmediate, and returns — all without waiting. The DB
result, the setTimeout callback, and the setImmediate callback are all in flight
concurrently, each racing to their respective phase.
await continuation is then a
microtask — it runs inside the current microtask drain window,
before the next I/O callback, not in a separate loop cycle.
writeFile comes from node:fs/promises, it returns
a Promise. When libuv finishes the disk write, the Poll I/O event resolves that
Promise. The await continuation — including
console.log('log.txt saved successfully') — runs as a
microtask in the same drain window, before any other I/O callback.
With the legacy fs.writeFile(cb) the completion would arrive as a
separate Poll event in its own loop cycle.
await fetch() and
await response.json() drain in Cycle N. After writeFile's
I/O event fires in Cycle N+1, await writeFile() drains as its own
microtask — each step uninterrupted by any other I/O callback.
writeFile is imported from node:fs/promises — it returns a
Promise, not a callback. When libuv completes the disk write, the Poll I/O event fires
and resolves the Promise. The await writeFile() continuation is then a
Promise microtask — it runs inside the microtask drain window triggered
by that Poll event, before any other I/O callback can execute.
This is in contrast to the legacy fs.writeFile(fn, cb) API, where the
completion callback would be a separate Poll event in its own loop iteration.