1'use strict';
2
3const {
4  Array,
5  FunctionPrototypeBind,
6} = primordials;
7
8const {
9  // For easy access to the nextTick state in the C++ land,
10  // and to avoid unnecessary calls into JS land.
11  tickInfo,
12  // Used to run V8's micro task queue.
13  runMicrotasks,
14  setTickCallback,
15  enqueueMicrotask,
16} = internalBinding('task_queue');
17
18const {
19  setHasRejectionToWarn,
20  hasRejectionToWarn,
21  listenForRejections,
22  processPromiseRejections,
23} = require('internal/process/promises');
24
25const {
26  getDefaultTriggerAsyncId,
27  newAsyncId,
28  initHooksExist,
29  destroyHooksExist,
30  emitInit,
31  emitBefore,
32  emitAfter,
33  emitDestroy,
34  symbols: { async_id_symbol, trigger_async_id_symbol },
35} = require('internal/async_hooks');
36const FixedQueue = require('internal/fixed_queue');
37
38const {
39  validateFunction,
40} = require('internal/validators');
41
42const { AsyncResource } = require('async_hooks');
43
44// *Must* match Environment::TickInfo::Fields in src/env.h.
45const kHasTickScheduled = 0;
46
47function hasTickScheduled() {
48  return tickInfo[kHasTickScheduled] === 1;
49}
50
51function setHasTickScheduled(value) {
52  tickInfo[kHasTickScheduled] = value ? 1 : 0;
53}
54
55const queue = new FixedQueue();
56
57// Should be in sync with RunNextTicksNative in node_task_queue.cc
58function runNextTicks() {
59  if (!hasTickScheduled() && !hasRejectionToWarn())
60    runMicrotasks();
61  if (!hasTickScheduled() && !hasRejectionToWarn())
62    return;
63
64  processTicksAndRejections();
65}
66
67function processTicksAndRejections() {
68  let tock;
69  do {
70    while ((tock = queue.shift()) !== null) {
71      const asyncId = tock[async_id_symbol];
72      emitBefore(asyncId, tock[trigger_async_id_symbol], tock);
73
74      try {
75        const callback = tock.callback;
76        if (tock.args === undefined) {
77          callback();
78        } else {
79          const args = tock.args;
80          switch (args.length) {
81            case 1: callback(args[0]); break;
82            case 2: callback(args[0], args[1]); break;
83            case 3: callback(args[0], args[1], args[2]); break;
84            case 4: callback(args[0], args[1], args[2], args[3]); break;
85            default: callback(...args);
86          }
87        }
88      } finally {
89        if (destroyHooksExist())
90          emitDestroy(asyncId);
91      }
92
93      emitAfter(asyncId);
94    }
95    runMicrotasks();
96  } while (!queue.isEmpty() || processPromiseRejections());
97  setHasTickScheduled(false);
98  setHasRejectionToWarn(false);
99}
100
101// `nextTick()` will not enqueue any callback when the process is about to
102// exit since the callback would not have a chance to be executed.
103function nextTick(callback) {
104  validateFunction(callback, 'callback');
105
106  if (process._exiting)
107    return;
108
109  let args;
110  switch (arguments.length) {
111    case 1: break;
112    case 2: args = [arguments[1]]; break;
113    case 3: args = [arguments[1], arguments[2]]; break;
114    case 4: args = [arguments[1], arguments[2], arguments[3]]; break;
115    default:
116      args = new Array(arguments.length - 1);
117      for (let i = 1; i < arguments.length; i++)
118        args[i - 1] = arguments[i];
119  }
120
121  if (queue.isEmpty())
122    setHasTickScheduled(true);
123  const asyncId = newAsyncId();
124  const triggerAsyncId = getDefaultTriggerAsyncId();
125  const tickObject = {
126    [async_id_symbol]: asyncId,
127    [trigger_async_id_symbol]: triggerAsyncId,
128    callback,
129    args,
130  };
131  if (initHooksExist())
132    emitInit(asyncId, 'TickObject', triggerAsyncId, tickObject);
133  queue.push(tickObject);
134}
135
136function runMicrotask() {
137  this.runInAsyncScope(() => {
138    const callback = this.callback;
139    try {
140      callback();
141    } finally {
142      this.emitDestroy();
143    }
144  });
145}
146
147const defaultMicrotaskResourceOpts = { requireManualDestroy: true };
148
149function queueMicrotask(callback) {
150  validateFunction(callback, 'callback');
151
152  const asyncResource = new AsyncResource(
153    'Microtask',
154    defaultMicrotaskResourceOpts,
155  );
156  asyncResource.callback = callback;
157
158  enqueueMicrotask(FunctionPrototypeBind(runMicrotask, asyncResource));
159}
160
161module.exports = {
162  setupTaskQueue() {
163    // Sets the per-isolate promise rejection callback
164    listenForRejections();
165    // Sets the callback to be run in every tick.
166    setTickCallback(processTicksAndRejections);
167    return {
168      nextTick,
169      runNextTicks,
170    };
171  },
172  queueMicrotask,
173};
174