1// Copyright Joyent, Inc. and other Node contributors. 2// 3// Permission is hereby granted, free of charge, to any person obtaining a 4// copy of this software and associated documentation files (the 5// "Software"), to deal in the Software without restriction, including 6// without limitation the rights to use, copy, modify, merge, publish, 7// distribute, sublicense, and/or sell copies of the Software, and to permit 8// persons to whom the Software is furnished to do so, subject to the 9// following conditions: 10// 11// The above copyright notice and this permission notice shall be included 12// in all copies or substantial portions of the Software. 13// 14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20// USE OR OTHER DEALINGS IN THE SOFTWARE. 21 22'use strict'; 23 24const { 25 MathTrunc, 26 ObjectCreate, 27 ObjectDefineProperty, 28 SymbolDispose, 29 SymbolToPrimitive, 30} = primordials; 31 32const { 33 immediateInfo, 34 toggleImmediateRef, 35} = internalBinding('timers'); 36const L = require('internal/linkedlist'); 37const { 38 async_id_symbol, 39 Timeout, 40 Immediate, 41 decRefCount, 42 immediateInfoFields: { 43 kCount, 44 kRefCount, 45 }, 46 kRefed, 47 kHasPrimitive, 48 getTimerDuration, 49 timerListMap, 50 timerListQueue, 51 immediateQueue, 52 active, 53 unrefActive, 54 insert, 55} = require('internal/timers'); 56const { 57 promisify: { custom: customPromisify }, 58 deprecate, 59} = require('internal/util'); 60let debug = require('internal/util/debuglog').debuglog('timer', (fn) => { 61 debug = fn; 62}); 63const { validateFunction } = require('internal/validators'); 64 65let timersPromises; 66 67const { 68 destroyHooksExist, 69 // The needed emit*() functions. 70 emitDestroy, 71} = require('internal/async_hooks'); 72 73// This stores all the known timer async ids to allow users to clearTimeout and 74// clearInterval using those ids, to match the spec and the rest of the web 75// platform. 76const knownTimersById = ObjectCreate(null); 77 78// Remove a timer. Cancels the timeout and resets the relevant timer properties. 79function unenroll(item) { 80 if (item._destroyed) 81 return; 82 83 item._destroyed = true; 84 85 if (item[kHasPrimitive]) 86 delete knownTimersById[item[async_id_symbol]]; 87 88 // Fewer checks may be possible, but these cover everything. 89 if (destroyHooksExist() && item[async_id_symbol] !== undefined) 90 emitDestroy(item[async_id_symbol]); 91 92 L.remove(item); 93 94 // We only delete refed lists because unrefed ones are incredibly likely 95 // to come from http and be recreated shortly after. 96 // TODO: Long-term this could instead be handled by creating an internal 97 // clearTimeout that makes it clear that the list should not be deleted. 98 // That function could then be used by http and other similar modules. 99 if (item[kRefed]) { 100 // Compliment truncation during insert(). 101 const msecs = MathTrunc(item._idleTimeout); 102 const list = timerListMap[msecs]; 103 if (list !== undefined && L.isEmpty(list)) { 104 debug('unenroll: list empty'); 105 timerListQueue.removeAt(list.priorityQueuePosition); 106 delete timerListMap[list.msecs]; 107 } 108 109 decRefCount(); 110 } 111 112 // If active is called later, then we want to make sure not to insert again 113 item._idleTimeout = -1; 114} 115 116// Make a regular object able to act as a timer by setting some properties. 117// This function does not start the timer, see `active()`. 118// Using existing objects as timers slightly reduces object overhead. 119function enroll(item, msecs) { 120 msecs = getTimerDuration(msecs, 'msecs'); 121 122 // If this item was already in a list somewhere 123 // then we should unenroll it from that 124 if (item._idleNext) unenroll(item); 125 126 L.init(item); 127 item._idleTimeout = msecs; 128} 129 130 131/** 132 * Schedules the execution of a one-time `callback` 133 * after `after` milliseconds. 134 * @param {Function} callback 135 * @param {number} [after] 136 * @param {any} [arg1] 137 * @param {any} [arg2] 138 * @param {any} [arg3] 139 * @returns {Timeout} 140 */ 141function setTimeout(callback, after, arg1, arg2, arg3) { 142 validateFunction(callback, 'callback'); 143 144 let i, args; 145 switch (arguments.length) { 146 // fast cases 147 case 1: 148 case 2: 149 break; 150 case 3: 151 args = [arg1]; 152 break; 153 case 4: 154 args = [arg1, arg2]; 155 break; 156 default: 157 args = [arg1, arg2, arg3]; 158 for (i = 5; i < arguments.length; i++) { 159 // Extend array dynamically, makes .apply run much faster in v6.0.0 160 args[i - 2] = arguments[i]; 161 } 162 break; 163 } 164 165 const timeout = new Timeout(callback, after, args, false, true); 166 insert(timeout, timeout._idleTimeout); 167 168 return timeout; 169} 170 171ObjectDefineProperty(setTimeout, customPromisify, { 172 __proto__: null, 173 enumerable: true, 174 get() { 175 if (!timersPromises) 176 timersPromises = require('timers/promises'); 177 return timersPromises.setTimeout; 178 }, 179}); 180 181/** 182 * Cancels a timeout. 183 * @param {Timeout | string | number} timer 184 * @returns {void} 185 */ 186function clearTimeout(timer) { 187 if (timer && timer._onTimeout) { 188 timer._onTimeout = null; 189 unenroll(timer); 190 return; 191 } 192 if (typeof timer === 'number' || typeof timer === 'string') { 193 const timerInstance = knownTimersById[timer]; 194 if (timerInstance !== undefined) { 195 timerInstance._onTimeout = null; 196 unenroll(timerInstance); 197 } 198 } 199} 200 201/** 202 * Schedules repeated execution of `callback` 203 * every `repeat` milliseconds. 204 * @param {Function} callback 205 * @param {number} [repeat] 206 * @param {any} [arg1] 207 * @param {any} [arg2] 208 * @param {any} [arg3] 209 * @returns {Timeout} 210 */ 211function setInterval(callback, repeat, arg1, arg2, arg3) { 212 validateFunction(callback, 'callback'); 213 214 let i, args; 215 switch (arguments.length) { 216 // fast cases 217 case 1: 218 case 2: 219 break; 220 case 3: 221 args = [arg1]; 222 break; 223 case 4: 224 args = [arg1, arg2]; 225 break; 226 default: 227 args = [arg1, arg2, arg3]; 228 for (i = 5; i < arguments.length; i++) { 229 // Extend array dynamically, makes .apply run much faster in v6.0.0 230 args[i - 2] = arguments[i]; 231 } 232 break; 233 } 234 235 const timeout = new Timeout(callback, repeat, args, true, true); 236 insert(timeout, timeout._idleTimeout); 237 238 return timeout; 239} 240 241/** 242 * Cancels an interval. 243 * @param {Timeout | string | number} timer 244 * @returns {void} 245 */ 246function clearInterval(timer) { 247 // clearTimeout and clearInterval can be used to clear timers created from 248 // both setTimeout and setInterval, as specified by HTML Living Standard: 249 // https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#dom-setinterval 250 clearTimeout(timer); 251} 252 253Timeout.prototype.close = function() { 254 clearTimeout(this); 255 return this; 256}; 257 258Timeout.prototype[SymbolDispose] = function() { 259 clearTimeout(this); 260}; 261 262/** 263 * Coerces a `Timeout` to a primitive. 264 * @returns {number} 265 */ 266Timeout.prototype[SymbolToPrimitive] = function() { 267 const id = this[async_id_symbol]; 268 if (!this[kHasPrimitive]) { 269 this[kHasPrimitive] = true; 270 knownTimersById[id] = this; 271 } 272 return id; 273}; 274 275/** 276 * Schedules the immediate execution of `callback` 277 * after I/O events' callbacks. 278 * @param {Function} callback 279 * @param {any} [arg1] 280 * @param {any} [arg2] 281 * @param {any} [arg3] 282 * @returns {Immediate} 283 */ 284function setImmediate(callback, arg1, arg2, arg3) { 285 validateFunction(callback, 'callback'); 286 287 let i, args; 288 switch (arguments.length) { 289 // fast cases 290 case 1: 291 break; 292 case 2: 293 args = [arg1]; 294 break; 295 case 3: 296 args = [arg1, arg2]; 297 break; 298 default: 299 args = [arg1, arg2, arg3]; 300 for (i = 4; i < arguments.length; i++) { 301 // Extend array dynamically, makes .apply run much faster in v6.0.0 302 args[i - 1] = arguments[i]; 303 } 304 break; 305 } 306 307 return new Immediate(callback, args); 308} 309 310ObjectDefineProperty(setImmediate, customPromisify, { 311 __proto__: null, 312 enumerable: true, 313 get() { 314 if (!timersPromises) 315 timersPromises = require('timers/promises'); 316 return timersPromises.setImmediate; 317 }, 318}); 319 320/** 321 * Cancels an immediate. 322 * @param {Immediate} immediate 323 * @returns {void} 324 */ 325function clearImmediate(immediate) { 326 if (!immediate || immediate._destroyed) 327 return; 328 329 immediateInfo[kCount]--; 330 immediate._destroyed = true; 331 332 if (immediate[kRefed] && --immediateInfo[kRefCount] === 0) 333 toggleImmediateRef(false); 334 immediate[kRefed] = null; 335 336 if (destroyHooksExist() && immediate[async_id_symbol] !== undefined) { 337 emitDestroy(immediate[async_id_symbol]); 338 } 339 340 immediate._onImmediate = null; 341 342 immediateQueue.remove(immediate); 343} 344 345Immediate.prototype[SymbolDispose] = function() { 346 clearImmediate(this); 347}; 348 349module.exports = { 350 setTimeout, 351 clearTimeout, 352 setImmediate, 353 clearImmediate, 354 setInterval, 355 clearInterval, 356 _unrefActive: deprecate( 357 unrefActive, 358 'timers._unrefActive() is deprecated.' + 359 ' Please use timeout.refresh() instead.', 360 'DEP0127'), 361 active: deprecate( 362 active, 363 'timers.active() is deprecated. Please use timeout.refresh() instead.', 364 'DEP0126'), 365 unenroll: deprecate( 366 unenroll, 367 'timers.unenroll() is deprecated. Please use clearTimeout instead.', 368 'DEP0096'), 369 enroll: deprecate( 370 enroll, 371 'timers.enroll() is deprecated. Please use setTimeout instead.', 372 'DEP0095'), 373}; 374