/lib/timers.js

https://github.com/bmeck/node · JavaScript · 362 lines · 225 code · 42 blank · 95 comment · 28 complexity · fa9eb6d3cc8ecffe095b01fc905e5489 MD5 · raw file

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