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