Exercise: Workout Interval Timer

Build the engine of a workout interval timer, a plan of work and rest, a clock display and a countdown that runs on setInterval, and let tests with a pretend clock check it in a split second.

Journeyman · JavaScript · Updated

Interval training is how a lot of people work out: 30 seconds of jumping jacks, 10 seconds of rest, again and again for a few rounds. Doing it with a phone's stopwatch means a lot of tapping with sweaty hands, so a timer that runs the whole workout by itself is worth having. The page is ready: it has the settings, a Start and a Stop button, and a big clock that turns red for work and green for rest. What it needs is the engine underneath, three functions for you to write, and tests check every one as you go.

Get the exercise code

In the Exercise box beside the lesson, choose Get the starter on GitHub to open its GitHub repository. Click Code, then Download ZIP, and unzip it where you keep your practice folders. Or clone it with Git.

The folder holds:

  • timer.js: the three functions, for you to finish.
  • timer.test.js: the tests that check your functions. You don't need to change it.
  • workout.js: runs a short workout in the terminal with your functions. Ready to use.
  • index.html, style.css and app.js: the timer's page, and the code that connects it to your functions. Ready to use.
  • package.json: tells Node.js how to run the code and its tests. Ready to use.

Open the folder in VS Code, and open a terminal there with Terminal > New Terminal.

What to do

Every function is already declared, with its parameters, and returns a placeholder for now. The comment above each one says what it should do, with examples:

// 2. formatClock(seconds)
//    Return a number of seconds the way a clock shows it: minutes, a colon, then two digits of seconds.
//    formatClock(30) -> '0:30'
//    formatClock(5) -> '0:05'
//    formatClock(65) -> '1:05'
//    formatClock(600) -> '10:00'
export function formatClock(seconds) {
  return '';
}
  1. Finish intervalPlan(rounds, workSeconds, restSeconds). It returns the workout as an array of steps, such as { name: 'Work', round: 1, seconds: 30 }. Every round is a Work step and then a Rest step, except the last round, which has no rest. It's a pure function: it works only from its arguments and changes nothing outside itself, so the same arguments always give the same plan.
  2. Finish formatClock(seconds). It turns a number of seconds into what the clock shows, from 0:05 to 10:00.
  3. Finish startTimer(plan, onTick, onDone), with setInterval. It calls onTick right away for the first step, then once a second as it counts down, moving on to the next step when one runs out. At the end it stops its interval and calls onDone once. It returns a function that stops the timer early, which the page's Stop button calls.

Start with the first two: they have no timers in them at all. startTimer is the one that waits.

Check your work

Run the tests from the terminal:

node --test

At first all 11 tests fail. That's expected: each function you finish turns its tests green. When a test fails, its message says what it expected, for example 5 seconds shows as 0:05: the seconds always have two digits.

A workout lasts minutes, but the tests take a split second. They replace setInterval with a pretend clock, called a mock timer, and move it forward a second at a time with t.mock.timers.tick(1000), so no test ever really waits. One thing follows from that: the pretend clock only knows about setInterval, so startTimer has to use it (a chain of setTimeout calls won't pass).

To see your functions run a short workout in real time, two rounds of 3 seconds of work and 2 of rest:

node workout.js

Then open index.html with Live Preview in your browser, set a workout and press Start. Check the page against this list:

  • The clock shows the first step's full time as soon as you press Start, then counts down once a second.
  • The box turns red for work and green for rest, and the line above the clock says which round you're on.
  • After the last round there's no rest: the page says Workout complete! Great job.
  • Stop stops the clock at once, and Start starts a new workout from the top.

Hints

  • Work out a small plan by hand first. For 3 rounds of 20 seconds' work and 15 seconds' rest, write the steps down in order. How many are there, and why isn't it 6?
  • A for loop builds the plan. Count the rounds from 1, push a Work step each time, and use an if to decide whether this round gets a Rest step after it.
  • Two numbers make the clock: the whole minutes, and the seconds left over. Math.floor rounds down (you used it in Parameters and Return Values), and % gives a remainder. padStart, from String Methods, gives 5 its leading zero.
  • Keep the timer's ID. startTimer needs it twice: to stop at the end, and inside the function it returns. Timers: setTimeout and setInterval shows an interval that stops itself.
  • Two variables keep your place: which step you're on, and how many seconds that step has left. Each tick changes one or both.
  • Call the callbacks, don't print. startTimer never uses console.log: it hands each tick to onTick, and workout.js and the page decide how to show it.

When you're done

All 11 tests are green, and node workout.js prints one line a second, then the last line:

Workout: 3 steps
Round 1 of 2 | Work | 0:03
Round 1 of 2 | Work | 0:02
Round 1 of 2 | Work | 0:01
Round 1 of 2 | Rest | 0:02
Round 1 of 2 | Rest | 0:01
Round 2 of 2 | Work | 0:03
Round 2 of 2 | Work | 0:02
Round 2 of 2 | Work | 0:01
Workout complete! Great job.

Now look back at startTimer. Could the same function run a different kind of plan, say the steps of a recipe, or the turns of a board game, without any change? If it only knows about steps with seconds, and leaves the showing to onTick, the answer is yes.

So what

You've built the part of a timer app that every countdown shares: a plan worked out by a pure function, a display format, and a loop that runs on time without freezing the page. The same pieces run a trivia game's 15-second countdown, a game's level clock and a "your session ends in 2:00" warning.

Key terms

Related