# Node.js API compatibility - Timers

The `timers` module in Node.js provides a set of functions for scheduling the execution of code after a specified delay or at regular intervals. It's essential for managing asynchronous operations and controlling the timing of function execution within applications. This module is available in the Azion Runtime through Node.js compatibility, and it's commonly used in functions to defer work, add small delays, or coordinate the timing of asynchronous tasks with `setTimeout`.

---

## Example: Basic setTimeout

The example below shows how to use the `timers` module in a function:

```javascript
/**
 * An example of using Node.js Timers API in an Azion Function.
 * Support:
 * - Partially supported (Extended by library `timers-browserify`)
 * @module runtime-apis/nodejs/timers/main
 * @example
 * // Execute with Azion Bundler:
 * npx edge-functions build
 *
 */
import timers from "node:timers";

/**
 * An example of using the Node.js Timers API in an Azion Function.
 * @param {*} event
 * @returns {Promise<Response>}
 */
const main = async (event) => {
  console.log("Hello, world!");
  console.log("Waiting for 5 seconds...");
  await new Promise((resolve) => timers.setTimeout(resolve, 2000));
  return new Response("Done!", { status: 200 });
};

export default main;
```

---

## Example: Delayed execution with setTimeout

Schedule operations to run after a delay:

```javascript
import timers from "node:timers";
import { promisify } from "node:util";

// Promisify setTimeout for async/await usage
const setTimeoutAsync = promisify(timers.setTimeout);

const main = async (event) => {
  const startTime = Date.now();
  const logs = [];

  logs.push(`Start: ${startTime}`);

  // Using promisified version - properly awaited
  await setTimeoutAsync(100);
  logs.push(`After 100ms delay`);

  // Multiple sequential delays
  await setTimeoutAsync(100);
  logs.push(`After additional 100ms`);

  // Calculate total time
  const totalTime = Date.now() - startTime;

  return new Response(JSON.stringify({
    logs,
    totalTimeMs: totalTime
  }), {
    headers: { "Content-Type": "application/json" }
  });
};

export default main;
```

---

## Example: setInterval for periodic operations

Execute code at regular intervals:

```javascript
import timers from "node:timers";

const main = async (event) => {
  const startTime = Date.now();
  const ticks = [];
  let tickCount = 0;

  // Create interval that runs every 50ms
  const intervalId = timers.setInterval(() => {
    tickCount++;
    const elapsed = Date.now() - startTime;
    ticks.push({ tick: tickCount, elapsed });
    console.log(`Tick ${tickCount} at ${elapsed}ms`);
  }, 50);

  // Run for 250ms then stop
  await new Promise(resolve => timers.setTimeout(resolve, 250));

  // Clear the interval
  timers.clearInterval(intervalId);

  console.log(`Total ticks: ${tickCount}`);

  return new Response(JSON.stringify({
    totalTicks: tickCount,
    ticks,
    durationMs: Date.now() - startTime
  }), {
    headers: { "Content-Type": "application/json" }
  });
};

export default main;
```

---

## Example: Immediate execution with setImmediate

Execute code immediately after the current event loop:

```javascript
import timers from "node:timers";

const main = async (event) => {
  const executionOrder = [];

  executionOrder.push("1. Start of function");

  // setImmediate runs after current code completes
  timers.setImmediate(() => {
    executionOrder.push("setImmediate callback");
  });

  // setTimeout with 0 delay still goes through timer phase
  timers.setTimeout(() => {
    executionOrder.push("setTimeout(0) callback");
  }, 0);

  // Process.nextTick runs before setImmediate (if available)
  if (typeof process?.nextTick === "function") {
    process.nextTick(() => {
      executionOrder.push("nextTick callback");
    });
  }

  executionOrder.push("2. End of synchronous code");

  // Wait for all callbacks to execute
  await new Promise(resolve => timers.setTimeout(resolve, 50));

  executionOrder.push("3. After waiting");

  // Note: The execution order between setImmediate and setTimeout(0)
  // may vary across environments and is not guaranteed.
  // In edge runtimes, this order can differ from Node.js.

  return new Response(JSON.stringify({
    executionOrder,
    note: "Order between setImmediate and setTimeout(0) may vary"
  }), {
    headers: { "Content-Type": "application/json" }
  });
};

export default main;
```

---

## Example: Timeout with cancellation

Implement cancellable timeouts for API calls:

```javascript
import timers from "node:timers";

const main = async (event) => {
  const results = [];

  // Create a timeout that can be cancelled
  const timeoutId = timers.setTimeout(() => {
    results.push("Timeout executed");
  }, 500);

  // Cancel the timeout before it fires
  timers.setTimeout(() => {
    results.push("Cancelling timeout");
    timers.clearTimeout(timeoutId);
  }, 200);

  // Wait to see what happens
  await new Promise(resolve => timers.setTimeout(resolve, 600));

  // Timeout with race pattern for API calls
  const fetchWithTimeout = async (url, timeoutMs) => {
    const controller = new AbortController();
    const timeout = timers.setTimeout(() => controller.abort(), timeoutMs);

    try {
      const response = await fetch(url, { signal: controller.signal });
      timers.clearTimeout(timeout);
      return { success: true, status: response.status };
    } catch (error) {
      timers.clearTimeout(timeout);
      return { success: false, error: error.message };
    }
  };

  // Test with a fast-responding API
  const apiResult = await fetchWithTimeout(
    "https://jsonplaceholder.typicode.com/todos/1",
    5000
  );
  results.push(`API result: ${JSON.stringify(apiResult)}`);

  return new Response(JSON.stringify({ results }), {
    headers: { "Content-Type": "application/json" }
  });
};

export default main;
```

---

## Example: Timer promises (timers/promises)

Use the modern promise-based timer API:

```javascript
import { setTimeout, setInterval, setImmediate } from "node:timers/promises";

const main = async (event) => {
  const startTime = Date.now();
  const logs = [];

  logs.push(`Start: ${startTime}`);

  // Promise-based setTimeout
  await setTimeout(100);
  logs.push(`After 100ms: ${Date.now() - startTime}ms elapsed`);

  // setTimeout with value
  const result = await setTimeout(50, "timer result");
  logs.push(`Got value: ${result}`);

  // Async iterator for setInterval with proper cancellation
  // Note: Use AbortController to prevent memory leaks
  let count = 0;
  const ac = new AbortController();
  const interval = setInterval(30, undefined, { signal: ac.signal });

  try {
    for await (const _ of interval) {
      count++;
      logs.push(`Interval tick ${count}`);
      if (count >= 3) {
        ac.abort(); // Properly cancel the interval
        break;
      }
    }
  } catch (error) {
    // AbortError is expected when we cancel
    if (error.name !== "AbortError") {
      throw error;
    }
  }

  // setImmediate as promise
  await setImmediate();
  logs.push("After setImmediate");

  // Calculate total time
  const totalTime = Date.now() - startTime;

  return new Response(JSON.stringify({
    logs,
    totalTimeMs: totalTime
  }), {
    headers: { "Content-Type": "application/json" }
  });
};

export default main;
```

---

## Example: Retry logic with exponential backoff

Implement retry patterns using timers:

```javascript
import timers from "node:timers";
import { promisify } from "node:util";

const setTimeoutAsync = promisify(timers.setTimeout);

// Retry with exponential backoff
const retryWithBackoff = async (fn, maxRetries, initialDelay = 100) => {
  let lastError;
  
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const result = await fn();
      return { success: true, result, attempts: attempt + 1 };
    } catch (error) {
      lastError = error;
      
      if (attempt < maxRetries - 1) {
        const delay = initialDelay * Math.pow(2, attempt);
        console.log(`Attempt ${attempt + 1} failed, retrying in ${delay}ms`);
        await setTimeoutAsync(delay);
      }
    }
  }
  
  return { success: false, error: lastError.message, attempts: maxRetries };
};

const main = async (event) => {
  // Simulate a flaky operation
  let attempts = 0;
  const flakyOperation = async () => {
    attempts++;
    if (attempts < 3) {
      throw new Error(`Attempt ${attempts} failed`);
    }
    return `Success on attempt ${attempts}`;
  };

  const result = await retryWithBackoff(flakyOperation, 5, 50);

  // Simulate a timeout scenario
  const timeoutOperation = async () => {
    await setTimeoutAsync(200);
    return "This should not appear";
  };

  const timeoutResult = await Promise.race([
    timeoutOperation(),
    setTimeoutAsync(50).then(() => ({ timedOut: true }))
  ]);

  return new Response(JSON.stringify({
    retryResult: result,
    timeoutResult
  }), {
    headers: { "Content-Type": "application/json" }
  });
};

export default main;
```

---

## Supported APIs

| API | Status |
|-----|--------|
| `setTimeout()` | 🟢 Supported |
| `clearTimeout()` | 🟢 Supported |
| `setInterval()` | 🟢 Supported |
| `clearInterval()` | 🟢 Supported |
| `setImmediate()` | 🟢 Supported |
| `clearImmediate()` | 🟢 Supported |
| `timers/promises.setTimeout()` | 🟢 Supported |
| `timers/promises.setInterval()` | 🟢 Supported |
| `timers/promises.setImmediate()` | 🟢 Supported |
| `timers.active()` | 🟡 Partially supported |
| `timers.enroll()` | 🔴 Not supported |
| `timers.unenroll()` | 🔴 Not supported |
| `timers.getActive()` | 🟡 Partially supported |

:::note
APIs marked as 🟡 Partially supported have limited functionality compared to the full Node.js implementation. The `enroll()` and `unenroll()` APIs are deprecated in Node.js and are not supported in Azion Runtime.
:::

---

## Related resources

- [Node.js `timers` documentation](https://nodejs.org/api/timers.html)
- [Node.js `timers/promises` documentation](https://nodejs.org/api/timers.html#timerspromises-api)