• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
37  ERR_INVALID_CALLBACK,
38  ERR_INVALID_ARG_TYPE
39} = require('internal/errors').codes;
40const FixedQueue = require('internal/fixed_queue');
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()) {
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  if (typeof callback !== 'function')
105    throw new ERR_INVALID_CALLBACK(callback);
106
107  if (process._exiting)
108    return;
109
110  let args;
111  switch (arguments.length) {
112    case 1: break;
113    case 2: args = [arguments[1]]; break;
114    case 3: args = [arguments[1], arguments[2]]; break;
115    case 4: args = [arguments[1], arguments[2], arguments[3]]; break;
116    default:
117      args = new Array(arguments.length - 1);
118      for (let i = 1; i < arguments.length; i++)
119        args[i - 1] = arguments[i];
120  }
121
122  if (queue.isEmpty())
123    setHasTickScheduled(true);
124  const asyncId = newAsyncId();
125  const triggerAsyncId = getDefaultTriggerAsyncId();
126  const tickObject = {
127    [async_id_symbol]: asyncId,
128    [trigger_async_id_symbol]: triggerAsyncId,
129    callback,
130    args
131  };
132  if (initHooksExist())
133    emitInit(asyncId, 'TickObject', triggerAsyncId, tickObject);
134  queue.push(tickObject);
135}
136
137function runMicrotask() {
138  this.runInAsyncScope(() => {
139    const callback = this.callback;
140    try {
141      callback();
142    } finally {
143      this.emitDestroy();
144    }
145  });
146}
147
148const defaultMicrotaskResourceOpts = { requireManualDestroy: true };
149
150function queueMicrotask(callback) {
151  if (typeof callback !== 'function') {
152    throw new ERR_INVALID_ARG_TYPE('callback', 'function', callback);
153  }
154
155  const asyncResource = new AsyncResource(
156    'Microtask',
157    defaultMicrotaskResourceOpts
158  );
159  asyncResource.callback = callback;
160
161  enqueueMicrotask(FunctionPrototypeBind(runMicrotask, asyncResource));
162}
163
164module.exports = {
165  setupTaskQueue() {
166    // Sets the per-isolate promise rejection callback
167    listenForRejections();
168    // Sets the callback to be run in every tick.
169    setTickCallback(processTicksAndRejections);
170    return {
171      nextTick,
172      runNextTicks
173    };
174  },
175  queueMicrotask
176};
177