Chapter 4 · Real programs
Waiting: the event loop
Step 1 of 22 · Concept
Programs that wait
By the end of this lesson your programs wait without stopping. You will:
- run a function later, with a timer,
- wait for a value with
awaitwhile other work goes on, - read and write files that outlast the program.
Step 2 of 22 · Demo
Later means after the end
setTimeout(fn, ms) hands the function fn to Node, with a delay in milliseconds, and returns at once. Node calls
fn once the delay has passed and the stack is empty.
Run it: start and end print first.
Step 3 of 22 · Concept
The event loop
One stack runs your JavaScript, one call at a time. A timer waits in Node.
- When the timer is done, its callback joins the callback queue.
- The event loop takes the queue's next callback once the stack is empty. That is after the script, or while
it waits at an
await(the part Promises and await). - The callbacks run one at a time.
Timers set together come due in the order of their delays.
Step 4 of 22 · Trace
Watch one timer go round
The program of the step Later means after the end. Press Next and watch the stack, Node and the queue.
function later() {
console.log("later");
}
console.log("start");
setTimeout(later, 100);
console.log("end");- Line 4 printed
start. Line 5 is next. - Line 5 handed
laterto Node, with a timer of 100 ms. It returned at once: line 6 is next. - Line 6 printed
end. The script is over, and the stack is empty. Line 2 runs once the timer is done. - 100 ms have passed:
laterjoins the callback queue. The stack is empty, so the loop takes it next. - A frame for
lateris on the stack. Line 2 is next. - Line 2 printed
later. Nothing is waiting any more, so Node exits.
Step 5 of 22 · Quiz
Which timer first?
Four timers, set in this order. Type the two missing lines.
setTimeout(() => console.log("300 ms"), 300);
setTimeout(() => console.log("100 ms"), 100);
setTimeout(() => console.log("100 ms, second"), 100);
setTimeout(() => console.log("0 ms"), 0);
console.log("main done");
Step 6 of 22 · Demo
See it run
Run it and compare with your answer. Run it again: the order is the same every time.
Step 7 of 22 · Demo
A long loop holds every callback back
A timer of 10 ms, then a loop that keeps the stack busy for 200 ms. Date.now() gives the time in milliseconds.
Run it: the timer's callback runs only after the loop, late.
Step 8 of 22 · Demo
setInterval repeats until clearInterval
setInterval(fn, ms) calls fn every ms milliseconds and gives back an id. clearInterval(id) stops it.
Run it: three ticks, then the interval stops and the program ends.
Step 9 of 22 · Exercise
Your turn: a countdown
Every 50 ms, log n and take 1 from it. Once n is 0, log Go! and stop the interval. The program must print:
3
2
1
Go!
Without clearInterval, the interval runs forever and the program never ends: press Stop. Keep the id that setInterval gives back.
const id = setInterval(() => { ... }, 50);. In the arrow function, an if (n > 0) logs and counts down; its else logs Go! and calls clearInterval(id).
} else {
console.log("Go!");
clearInterval(id);
}
Step 10 of 22 · Concept
A promise is a value that arrives later
A promise stands for a value that is not there yet. Work that takes time gives back a promise at once.
An async function always returns a promise, even when its value is ready:
async function answer() {
return 42;
}
console.log(answer());
console.log shows | means |
|---|---|
Promise { <pending> } | the value has not arrived yet |
Promise { 42 } | it has arrived: 42 |
This one prints Promise { 42 }, not 42.
Step 11 of 22 · Demo
await waits, the event loop goes on
await pauses its function until the promise has its value. The frame is put aside, and the rest goes on. Use
wait(ms) as it is.
Run it: meanwhile comes before tea ready.
Step 12 of 22 · Demo
Promise callbacks go before timers
The rest of a function after await is a callback too. It waits in a queue of its own, the promise queue. The event
loop empties that queue before it takes a timer's callback.
Run it: the 0 ms timer comes last.
Step 13 of 22 · Quiz
What does price + 1 give?
What does this program print?
async function getPrice() {
return 5;
}
const price = getPrice();
console.log(price + 1);
Step 14 of 22 · Try it
Try it: add the await
Run it: [object Promise]1. Put await before getPrice() so that price is the value, and run it again: 6.
Line 4 becomes const price = await getPrice();. A module may use await at its top level.
Step 15 of 22 · Demo
Wait for several at once
Both tasks start at once. Promise.all([...]) gives one promise for the array of their values.
Run it: fast finishes first, but the values keep the array's order.
Step 16 of 22 · Exercise
Your turn: tea and toast
- Write
async function make(name, ms): it awaitswait(ms), logs`${name} ready`and returnsname. - Start tea (100 ms) and toast (50 ms) together.
- Await both with
Promise.all, then logBreakfast:and the two names.
The program must print:
toast ready
tea ready
Breakfast: tea, toast
Awaiting tea, then toast, makes them one after the other: tea ready comes first. Start both before you await anything.
make is task of the step Wait for several at once with another line logged. Keep the two promises, then await Promise.all([tea, toast]) gives [ 'tea', 'toast' ].
console.log(`Breakfast: ${both.join(", ")}`);
Step 17 of 22 · Concept
Reading a file takes time, so it is a promise
import { readFile, writeFile } from "node:fs/promises";
await writeFile("tasks.json", text);
const back = await readFile("tasks.json", "utf8");
Storage is slow next to memory, so node:fs/promises gives promises: await them. writeFile saves text in a file
of the block's folder. The file stays after the program ends (lesson 2).
Step 18 of 22 · Demo
Save, read back, and miss
It saves two tasks as JSON (lesson 11) and reads the file back. Then it asks for a file that is not there. Run it.
Step 19 of 22 · Demo
A rejection nobody catches ends the program
When an async function throws, its promise is rejected. Here nobody awaits it. Run it: the script ends, then
Node stops with the error and exit code 1.
Step 20 of 22 · Check yourself
Check yourself: a saved high score
- Save the scores
[90, 75]as JSON inscores.json. - Read them back, add
95, and save them again. - Read the file once more, and log the best score and how many there are.
The program must print:
Best: 95
Saved 3 scores
Every file call needs its await. Read and parse the file each time: what you print must come from the file, not from your array.
Step 21 of 22 · Challenge · optional
Challenge: snooze the alarm
Optional.
The alarm rings in 100 ms. Snooze it: cancel it, log snoozed, and set a new alarm for 200 ms. The program must
print:
snoozed
ring
setTimeout gives back an id, as setInterval does. alarm holds it.
clearTimeout(id) cancels a timer before it runs, as clearInterval stops an interval.
clearTimeout(alarm);
Step 22 of 22 · Recap
Recap
setTimeout(fn, ms)runsfnlater, once the stack is empty;setIntervalrepeats untilclearInterval.- The event loop takes a callback only when the stack is empty: a long loop holds them back.
- An
async functionreturns a promise;awaitpauses the function while the event loop goes on. Promise.all([...])waits for several at once and keeps their order.readFileandwriteFilefromnode:fs/promisescopy text between memory and storage.
Next: lesson 20 puts the whole course into one console tool.
Review
2 questions from this lesson, in a new order each time. Answer from memory: 2 of 2 right on the first try passes. On a day after you finish the lesson, a pass earns 15 XP.
The review needs JavaScript.
function later() {
console.log("later");
}
console.log("start");
setTimeout(later, 100);
console.log("end");
setTimeout(() => console.log("300 ms"), 300);
setTimeout(() => console.log("100 ms"), 100);
setTimeout(() => console.log("100 ms, second"), 100);
setTimeout(() => console.log("0 ms"), 0);
console.log("main done");
const start = Date.now();
setTimeout(() => {
console.log("timer: late?", Date.now() - start >= 200);
}, 10);
while (Date.now() - start < 200) {
// busy: nothing else can run
}
console.log("loop done");
let n = 0;
const id = setInterval(() => {
n++;
console.log("tick", n);
if (n === 3) {
clearInterval(id);
}
}, 50);
console.log("interval set");
let n = 3;
// every 50 ms: log n and take 1 from it;
// when n is 0, log Go! and clear the interval
function wait(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function brew(drink) {
console.log("brewing", drink);
await wait(100);
console.log(drink, "ready");
}
brew("tea");
console.log("meanwhile");
setTimeout(() => console.log("timer, 0 ms"), 0);
async function step() {
await null;
console.log("after await");
}
step();
console.log("script done");
async function getPrice() {
return 5;
}
const price = getPrice();
console.log(price + 1);
function wait(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function task(name, ms) {
await wait(ms);
console.log(name, "done");
return name;
}
const slow = task("slow", 200);
const fast = task("fast", 50);
const results = await Promise.all([slow, fast]);
console.log(results);
function wait(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
// 1. make(name, ms)
// 2. start tea (100 ms) and toast (50 ms)
// 3. await both, then log Breakfast: tea, toast
import { readFile, writeFile } from "node:fs/promises";
const tasks = ["Buy milk", "Write report"];
await writeFile("tasks.json", JSON.stringify(tasks));
const text = await readFile("tasks.json", "utf8");
const again = JSON.parse(text);
console.log(again.length, "tasks:", again.join(", "));
try {
await readFile("nope.json", "utf8");
} catch (e) {
console.log(e.code);
}
async function load() {
throw new Error("no file");
}
load();
console.log("after the call");
import { readFile, writeFile } from "node:fs/promises";
// 1. save [90, 75] as JSON in scores.json
// 2. read them back, add 95, save again
// 3. read once more: log the best and the count
function ring() {
console.log("ring");
}
const alarm = setTimeout(ring, 100);
// snooze: cancel alarm, log snoozed,
// and set a new alarm for 200 ms