process.nextTick(callback, ...args?): void
queueMicrotask() instead.process.nextTick() adds callback to the "next tick queue". This queue is
fully drained after the current operation on the JavaScript stack runs to
completion and before the event loop is allowed to continue. It's possible to
create an infinite loop if one were to recursively call process.nextTick().
See the Event Loop guide for more background.
import { nextTick } from 'node:process'; console.log('start'); nextTick(() => { console.log('nextTick callback'); }); console.log('scheduled'); // Output: // start // scheduled // nextTick callback
const { nextTick } = require('node:process'); console.log('start'); nextTick(() => { console.log('nextTick callback'); }); console.log('scheduled'); // Output: // start // scheduled // nextTick callback
This is important when developing APIs in order to give users the opportunity to assign event handlers after an object has been constructed but before any I/O has occurred:
import { nextTick } from 'node:process'; function MyThing(options) { this.setupOptions(options); nextTick(() => { this.startDoingStuff(); }); } const thing = new MyThing(); thing.getReadyForStuff(); // thing.startDoingStuff() gets called now, not before.
const { nextTick } = require('node:process'); function MyThing(options) { this.setupOptions(options); nextTick(() => { this.startDoingStuff(); }); } const thing = new MyThing(); thing.getReadyForStuff(); // thing.startDoingStuff() gets called now, not before.
It is very important for APIs to be either 100% synchronous or 100% asynchronous. Consider this example:
// WARNING! DO NOT USE! BAD UNSAFE HAZARD! function maybeSync(arg, cb) { if (arg) { cb(); return; } fs.stat('file', cb); }
This API is hazardous because in the following case:
const maybeTrue = Math.random() > 0.5; maybeSync(maybeTrue, () => { foo(); }); bar();
It is not clear whether foo() or bar() will be called first.
The following approach is much better:
import { nextTick } from 'node:process'; function definitelyAsync(arg, cb) { if (arg) { nextTick(cb); return; } fs.stat('file', cb); }
const { nextTick } = require('node:process'); function definitelyAsync(arg, cb) { if (arg) { nextTick(cb); return; } fs.stat('file', cb); }
The queueMicrotask() API is an alternative to process.nextTick() that instead of using the
"next tick queue" defers execution of a function using the same microtask queue used to execute the
then, catch, and finally handlers of resolved promises.
Within Node.js, every time the "next tick queue" is drained, the microtask queue is drained immediately after.
So in CJS modules process.nextTick() callbacks are always run before queueMicrotask() ones.
However since ESM modules are processed already as part of the microtask queue, there
queueMicrotask() callbacks are always executed before process.nextTick() ones since Node.js
is already in the process of draining the microtask queue.
import { nextTick } from 'node:process'; Promise.resolve().then(() => console.log('resolve')); queueMicrotask(() => console.log('microtask')); nextTick(() => console.log('nextTick')); // Output: // resolve // microtask // nextTick
const { nextTick } = require('node:process'); Promise.resolve().then(() => console.log('resolve')); queueMicrotask(() => console.log('microtask')); nextTick(() => console.log('nextTick')); // Output: // nextTick // resolve // microtask
For most userland use cases, the queueMicrotask() API provides a portable
and reliable mechanism for deferring execution that works across multiple
JavaScript platform environments and should be favored over process.nextTick().
In simple scenarios, queueMicrotask() can be a drop-in replacement for
process.nextTick().
console.log('start'); queueMicrotask(() => { console.log('microtask callback'); }); console.log('scheduled'); // Output: // start // scheduled // microtask callback
One note-worthy difference between the two APIs is that process.nextTick()
allows specifying additional values that will be passed as arguments to the
deferred function when it is called. Achieving the same result with
queueMicrotask() requires using either a closure or a bound function:
function deferred(a, b) { console.log('microtask', a + b); } console.log('start'); queueMicrotask(deferred.bind(undefined, 1, 2)); console.log('scheduled'); // Output: // start // scheduled // microtask 3
There are minor differences in the way errors raised from within the next tick
queue and microtask queue are handled. Errors thrown within a queued microtask
callback should be handled within the queued callback when possible. If they are
not, the process.on('uncaughtException') event handler can be used to capture
and handle the errors.
When in doubt, unless the specific capabilities of process.nextTick() are
needed, use queueMicrotask().