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