JavaScript: Timer Params
Tags: javascript, webdev, programming
Published: 2024-04-20 15:52:35

When you want a message to disappear after ten seconds or limit how long to wait for data, [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout) and [`setInterval`](https://developer.mozilla.org/en-US/docs/Web/API/setInterval)  are probably the tools for the job. Small differences in how you use timers can create unexpected outcomes, but there are simple solutions.

## Timer Basics

First off, you should know:
1. Timers don't guarantee exact timing.
2. Timers can behave differently in different browsers or environments.
3. Timers have some history that limit their precision.

I've [written about timer issues before](https://dev.to/oculus42/javascript-timers-intervals-5fnl), if you are interested in a deeper dive.

## Basic Timers, Basic Bugs

Let's see a simple timer bug. Here are two _nearly identical_ functions:

```javascript
const one = () => {
  for (let i = 0; i < 5; i += 1) {
    setTimeout(() => console.log('one', i), i);
  }
}

const two = () => {
  let i;
  for (i = 0; i < 5; i += 1) {
    setTimeout(() => console.log('two', i), i);
  }
};

one();
two();
```

If you run this – I recommend using [RunJS](https://runjs.app) for running code like this locally – you'll see the following:

```javascript
'one' 0
'two' 5
'one' 1
'two' 5
'one' 2
'two' 5
'one' 3
'two' 5
'one' 4
'two' 5
```

This output can be confusing. How did we get five over and over, and why are the fives mixed in? Shouldn't they all be at the end?

### Block Scope

This is the difference in block scoping that ECMAScript 2015 introduced. In function `one`, we define `i` inside the `for` loop, so we get a distinct instance of `i` each loop. That instance is used immediately by `setTimeout`, and then _later_ by `console.log`. _Later_ is the key.

In function `two`, we defined `i` _outside_ the `for` loop, so we have a shared value of `i`. `setTimeout` uses the value immediately, so we have timers for 1, 2, 3, and 4 milliseconds, but `console.log` _accesses the value later_, after the loop has completed and the value of `i` no longer passes the condition `i < 5`.

In this simple example, we can move the variable declaration and be done, but real-world use is often more complicated. Fortunately, there's a simple solution! 

## Timers, Parameters, and Closures

[`setTimeout` and `setInterval` can pass arguments to their callbacks!](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#param1)

Rather than having to check the location of a variable declaration, we can just pass the value to `setTimeout` and consume it as a parameter in the callback.

```javascript
const three = () => {
  for (let i = 0; i < 5; i += 1) {
    // setTimeout(callback, delay, param1, ...)
    setTimeout((value) => console.log('three', value), i, i);
  }
}

const four = () => {
  let i;
  for (i = 0; i < 5; i += 1) {
    setTimeout((value) => console.log('four', value), i, i);
  }
};

three();
four();
```

Because this is passed to `setTimeout` directly and not accessed later by the function, it guarantees it uses the value available when `setTimeout` was called, even if the variable changes later.

```javascript
'three' 0
'four' 0
'three' 1
'four' 1
'three' 2
'four' 2
'three' 3
'four' 3
'three' 4
'four' 4
```

```javascript
setTimeout(console.log, 1000, 'You can pass', 'multiple arguments', 'to the callback', 'after the delay argument');
```

This can be useful when you need access to values from inside the calling function. We don't need a closure or anonymous function to provide access to variables when we can pass them directly. Knowing this, we can choose how we pass and access information.

```javascript
const five = () => {
  let sum = 0;
  let i;
  for (i = 0; i < 5; i += 1) {
    setTimeout((val) => {
      sum += val;
      console.log(`val: ${val}, i: ${i}, sum: ${sum}`);
    }, i, i);
  }
}

five();
```

```javascript
'val: 0, i: 5, sum: 0'
'val: 1, i: 5, sum: 1'
'val: 2, i: 5, sum: 3'
'val: 3, i: 5, sum: 6'
'val: 4, i: 5, sum: 10'
```

### Closures & Reusable Functions

Because we can pass closure values through `setTimeout`, we don't have to depend on anonymous functions created in the closure scope.

```javascript
const six = () => {
  for (let i = 0; i < 5; i += 1) {
    // setTimeout(callback, delay, param1, param2, ...)
    setTimeout(console.log, i, 'six', i);
  }
}

const seven = () => {
  let i;
  for (i = 0; i < 5; i += 1) {
    setTimeout(console.log, i, 'seven', i);
  }
};

six();
seven();
```

```javascript
'six' 0
'seven' 0
'six' 1
'seven' 1
'six' 2
'seven' 2
'six' 3
'seven' 3
'six' 4
'seven' 4
```

You can also pass arrays and objects in these parameters, but you must be mindful of mutation and re-assignment of variables in these cases.

## Conclusion

Using the optional parameters of timers makes it easier to re-use code and eliminate some complexity.

I hope you found this informational, or at least entertaining.