• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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