timers.js

/**
 * DOM Style Timers
 *
 * The Timers API provides functionality to allow developers to create DOM style timers.
 *
 * @see {@link https://www.w3schools.com/js/js_timing.asp}
 *
 * @module Timers
 */

import assert from 'assert';

const TIMEOUT_MAX = Math.pow(2, 31) - 1;

const binding = process.binding('timers');

let nextId = 1;

/**
 * This map keeps at sync the JavaScript timer IDs and their equivalent Rust
 * timer indexes (resource IDs) for all currently active timers.
 *
 * @ignore
 * @type {Map<number, number>}
 */

const activeTimers = new Map();

/**
 * Sets a timer which executes a function or specified piece of code once the
 * timer expires.
 *
 * @param {Function} callback - A function to be executed after the timer expires.
 * @param {Number} delay - The milliseconds that the timer should wait before the function is executed.
 * @param {...any} [args] - Additional arguments which are passed through to the function.
 * @returns {Number} The ID which identifies the timer created.
 */
export function setTimeout(callback, delay, ...args) {
  // Coalesce to number or NaN.
  delay *= 1;

  // Check delay's boundaries.
  if (!(delay >= 1 && delay <= TIMEOUT_MAX)) {
    delay = 1;
  }

  // Check if callback is a valid function.
  assert.isFunction(callback);

  // Pin down the correct ID value.
  const id = nextId++;

  const timer = binding.createTimeout(
    () => {
      callback(...args);
      activeTimers.delete(id);
    },
    delay,
    false
  );

  // Update `activeTimers` map.
  activeTimers.set(id, timer);

  return id;
}

/**
 * The global clearTimeout() method cancels a timeout previously established
 * by calling setTimeout().
 *
 * @param {Number} id - The ID which identifies the timer.
 */
export function clearTimeout(id) {
  // Check parameter's type.
  assert.integer(id);

  if (activeTimers.has(id)) {
    binding.removeTimeout(activeTimers.get(id));
    activeTimers.delete(id);
  }
}

/**
 * Repeatedly calls a function or executes a code snippet, with a fixed time
 * delay between each call.
 *
 * @param {Function} callback - A function to be executed every `delay` milliseconds.
 * @param {Number} delay - The milliseconds the timer should delay in between executions.
 * @param {...any} [args] - Additional arguments which are passed through to the function.
 * @returns {Number} The ID which identifies the timer created.
 */
export function setInterval(callback, delay, ...args) {
  // Coalesce to number or NaN.
  delay *= 1;

  // Check delay's boundaries.
  if (!(delay >= 1 && delay <= TIMEOUT_MAX)) {
    delay = 1;
  }

  // Check if callback is a valid function.
  assert.isFunction(callback);

  // Pin down the correct ID value.
  const id = nextId++;
  const timer = binding.createTimeout(callback, delay, true, args);

  // Update `activeTimers` map.
  activeTimers.set(id, timer);

  return id;
}

/**
 * The global clearInterval() method cancels an interval previously established
 * by calling setInterval().
 *
 * @param {Number} id - The ID which identifies the timer.
 */
export function clearInterval(id) {
  clearTimeout(id);
}

/**
 * Schedules the "immediate" execution of the callback after the I/O phase.
 *
 * @param {Function} callback - A function to be executed after the I/O (`poll`) phase.
 * @param  {...any} [args] - Additional arguments which are passed through to the function.
 * @returns {Number} The ID which identifies the timer created.
 */
export function setImmediate(callback, ...args) {
  // Check arg type.
  assert.isFunction(callback);

  // Pin down the correct ID value.
  const id = nextId++;
  const immediate = binding.createImmediate(() => {
    callback(...args);
    activeTimers.delete(id);
  });

  // Update `activeTimers` map.
  activeTimers.set(id, immediate);

  return id;
}

/**
 * Cancels an Immediate timer created by setImmediate().
 *
 * @param {Number} id - The ID which identifies the timer.
 */
export function clearImmediate(id) {
  // Check parameter's type.
  assert.integer(id);

  if (activeTimers.has(id)) {
    binding.removeImmediate(activeTimers.get(id));
    activeTimers.delete(id);
  }
}

export default {
  setTimeout,
  setInterval,
  setImmediate,
  clearTimeout,
  clearInterval,
  clearImmediate,
};