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