Skip to content

Commit bca84c2

Browse files
committed
process: add deferTick
Adds a new scheduling primitive to resolve zaldo when mixing traditional Node async programming with async/await and Promises. We cannot "fix" nextTick without breaking the whole ecosystem. nextTick usage should be discouraged and we should try to incrementally move to this new primitive. TODO: - [] Fill in concrete examples - [] Add tests - [] Add benchmarks - [] Do we need async hook logic or not? - [] process._exiting? - [] Anything else we are unhappy with in regard to nextTick which we want to fix?
1 parent 94f824a commit bca84c2

3 files changed

Lines changed: 52 additions & 1 deletion

File tree

doc/api/process.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1219,6 +1219,25 @@ const process = require('node:process');
12191219
process.debugPort = 5858;
12201220
```
12211221

1222+
## `process.deferTick(callback[, ...args])`
1223+
1224+
<!-- YAML
1225+
added: REPLACEME
1226+
-->
1227+
1228+
* `callback` {Function}
1229+
* `...args` {any} Additional arguments to pass when invoking the `callback`
1230+
1231+
`process.deferTick()` adds `callback` to the "defer tick queue". This queue is
1232+
fully drained after the current operation on the JavaScript stack runs to
1233+
completion and before the event loop is allowed to continue. It's possible to
1234+
create an infinite loop if one were to recursively call `process.deferTick()`.
1235+
See the [Event Loop][] guide for more background.
1236+
1237+
Unlike `process.nextTick`, `process.deferTick()` will run after the "next tick
1238+
queue" and the microtask queue has been fully drained as to avoid zaldo when
1239+
combinding traditional node asynchronous code with Promises.
1240+
12221241
## `process.disconnect()`
12231242

12241243
<!-- YAML

lib/internal/bootstrap/node.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,8 +303,9 @@ process.emitWarning = emitWarning;
303303
// bootstrap to make sure that any operation done before this are synchronous.
304304
// If any ticks or timers are scheduled before this they are unlikely to work.
305305
{
306-
const { nextTick, runNextTicks } = setupTaskQueue();
306+
const { nextTick, runNextTicks, deferTick } = setupTaskQueue();
307307
process.nextTick = nextTick;
308+
process.deferTick = deferTick;
308309
// Used to emulate a tick manually in the JS land.
309310
// A better name for this function would be `runNextTicks` but
310311
// it has been exposed to the process object so we keep this legacy name

lib/internal/process/task_queues.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ function setHasTickScheduled(value) {
5353
}
5454

5555
const queue = new FixedQueue();
56+
const deferQueue = new FixedQueue();
5657

5758
// Should be in sync with RunNextTicksNative in node_task_queue.cc
5859
function runNextTicks() {
@@ -93,6 +94,10 @@ function processTicksAndRejections() {
9394
emitAfter(asyncId);
9495
}
9596
runMicrotasks();
97+
98+
let tmp = queue;
99+
queue = deferQueue;
100+
deferQueue = tmp;
96101
} while (!queue.isEmpty() || processPromiseRejections());
97102
setHasTickScheduled(false);
98103
setHasRejectionToWarn(false);
@@ -133,6 +138,31 @@ function nextTick(callback) {
133138
queue.push(tickObject);
134139
}
135140

141+
function deferTick(callback, ...args) {
142+
validateFunction(callback, 'callback');
143+
144+
if (process._exiting)
145+
return;
146+
147+
if (tickInfo[kHasTickScheduled] === 0) {
148+
tickInfo[kHasTickScheduled] = 1;
149+
}
150+
151+
const asyncId = newAsyncId();
152+
const triggerAsyncId = getDefaultTriggerAsyncId();
153+
const tickObject = {
154+
[async_id_symbol]: asyncId,
155+
[trigger_async_id_symbol]: triggerAsyncId,
156+
callback,
157+
args,
158+
};
159+
160+
if (initHooksExist())
161+
emitInit(asyncId, 'TickObject', triggerAsyncId, tickObject);
162+
163+
deferQueue.push(tickObject);
164+
}
165+
136166
function runMicrotask() {
137167
this.runInAsyncScope(() => {
138168
const callback = this.callback;
@@ -166,6 +196,7 @@ module.exports = {
166196
setTickCallback(processTicksAndRejections);
167197
return {
168198
nextTick,
199+
deferTick,
169200
runNextTicks,
170201
};
171202
},

0 commit comments

Comments
 (0)