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