Aufwärmen · Aufgabe 1 von 7
Aufwärmen aus tech.javascript: In welcher Reihenfolge gibt dieses Programm aus?
setTimeout(() => console.log('timeout'), 0);
Promise.resolve().then(() => console.log('promise'));
console.log('sync');// B4.4 · ca. 31 Min. · Einstieg
Nach dieser Lektion planen, wiederholen und stoppen Sie Timer in Node.js, sagen voraus, wann setImmediate, process.nextTick und Promise-Callbacks laufen, und entscheiden, welche Timer Ihr Programm am Laufen halten dürfen.
Danach können Sie
Aufwärmen · Aufgabe 1 von 7
setTimeout(() => console.log('timeout'), 0);
Promise.resolve().then(() => console.log('promise'));
console.log('sync');Vorhersagen · Aufgabe 2 von 7
Promise.resolve().then(() => console.log('resolve'));
queueMicrotask(() => console.log('microtask'));
process.nextTick(() => console.log('nextTick'));
console.log('sync');Üben · Aufgabe 3 von 7
const timer = setTimeout(() => console.log('fired'), 300);
console.log(timer.hasRef());
timer.____();
console.log(timer.hasRef());
console.log('done');Üben · Aufgabe 4 von 7
Üben · Aufgabe 5 von 7
import {readFile} from 'node:fs';
readFile('main.js', () => {
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
});Denksport · Aufgabe 6 von 7
let n = 0;
setTimeout(() => console.log('timer sees', n), 0);
function spin() {
if (++n < 100000) process.nextTick(spin);
}
process.nextTick(spin);Anwenden · Aufgabe 7 von 7
Prüfen Sie Ihr Ergebnis anhand dieser Liste
Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.
Ausgearbeitetes Beispiel
Teil 1 plant ein nextTick, einen Microtask und einen Promise-Callback auf der obersten Ebene eines ES-Moduls. Teil 2 plant alle vier Arten in einem I/O-Callback, wo ihre Reihenfolge fest ist. Teil 3 startet einen Timer von einer Sekunde und ruft unref auf. Teil 4 durchläuft setInterval aus node:timers/promises dreimal. Das Programm plant setTimeout(0) und setImmediate nie gemeinsam im Hauptmodul, wo ihre Reihenfolge nicht fest ist.
main.js
import {readFile} from 'node:fs';
import {setInterval} from 'node:timers/promises';
// 1. The main module is an ES module: microtasks run before the next tick queue.
process.nextTick(() => console.log('main: nextTick'));
queueMicrotask(() => console.log('main: queueMicrotask'));
Promise.resolve().then(() => console.log('main: promise'));
console.log('main: sync code');
// 2. Inside an I/O callback the order is fixed.
readFile('main.js', () => {
setTimeout(() => console.log('io: setTimeout 0'), 0);
setImmediate(() => console.log('io: setImmediate'));
queueMicrotask(() => console.log('io: queueMicrotask'));
process.nextTick(() => console.log('io: nextTick'));
console.log('io: callback');
});
// 3. An unref'd timer does not keep the program alive.
setTimeout(() => console.log('never printed'), 1000).unref();
// 4. timers/promises: setInterval as an async iterator.
let ticks = 0;
for await (const label of setInterval(50, 'tick')) {
console.log(label, ++ticks);
if (ticks === 3) break; // break stops the interval
}
console.log('done; nothing keeps the loop alive');
Ausführen mit
node main.jsAusgabe
main: sync code
main: queueMicrotask
main: promise
main: nextTick
io: callback
io: nextTick
io: queueMicrotask
io: setImmediate
io: setTimeout 0
tick 1
tick 2
tick 3
done; nothing keeps the loop aliveÜbung 1 von 2
debounce(fn, ms) gibt eine Funktion zurück, die wartet: Jeder Aufruf bricht den zuvor geplanten ab und plant fn neu, ms Millisekunden später, mit den neuesten Argumenten. Schnelle Aufrufe führen also zu einem Aufruf von fn, mit den letzten Argumenten, nach einer ruhigen Pause von ms. Der Starter ruft fn jedes Mal sofort auf. Nutzen Sie setTimeout und clearTimeout. Dieser Teil läuft auch im Browser.
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser den JavaScript-Runner herunter (bis zu 0.1 MB) und speichert ihn im Cache. Ihr Code läuft in der eigenen Engine Ihres Browsers und bleibt auf Ihrem Gerät.
Halten Sie den Timeout in einer Variablen außerhalb der zurückgegebenen Funktion: let timer;
Rufen Sie in der zurückgegebenen Funktion zuerst clearTimeout(timer) auf. clearTimeout mit undefined tut nichts, also ist der erste Aufruf in Ordnung.
Dann timer = setTimeout(() => fn(...args), ms); die Pfeilfunktion behält die neuesten args.
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
// Returns a function that calls fn only after ms milliseconds without a new call,
// with the arguments of the last call.
export function debounce(fn, ms) {
let timer;
return (...args) => {
clearTimeout(timer); // cancels the call scheduled before, if any
timer = setTimeout(() => fn(...args), ms);
};
}
// Typing H, He, Hello quickly: only the last text should be saved.
const save = debounce((text) => console.log('saved:', text), 50);
save('H');
save('He');
save('Hello');
Installieren Sie Node.js 24 LTS oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.
main.js
// Returns a function that calls fn only after ms milliseconds without a new call,
// with the arguments of the last call.
export function debounce(fn, ms) {
return (...args) => fn(...args); // calls fn at once, every time
}
// Typing H, He, Hello quickly: only the last text should be saved.
const save = debounce((text) => console.log('saved:', text), 50);
save('H');
save('He');
save('Hello');
main.test.js
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {debounce} from './main.js';
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
test('drei schnelle Aufrufe führen zu einem Aufruf, mit den letzten Argumenten', async () => {
const calls = [];
const save = debounce((text) => calls.push(text), 30);
save('H');
save('He');
save('Hello');
await sleep(90);
assert.deepEqual(calls, ['Hello'], `fn wurde mit ${JSON.stringify(calls)} aufgerufen`);
});
test('fn wird nicht aufgerufen, bevor ms vergangen sind', async () => {
const calls = [];
const save = debounce((text) => calls.push(text), 30);
save('now?');
assert.equal(calls.length, 0, 'fn wurde sofort aufgerufen; es sollte warten');
await sleep(90);
assert.equal(calls.length, 1, `fn wurde nach der Pause ${calls.length}-mal aufgerufen; erwartet war einmal`);
});
test('Aufrufe mit einer Pause dazwischen werden alle ausgeführt', async () => {
const calls = [];
const save = debounce((text) => calls.push(text), 30);
save('a');
await sleep(90);
save('b');
await sleep(90);
assert.deepEqual(calls, ['a', 'b'], `fn wurde mit ${JSON.stringify(calls)} aufgerufen`);
});
package.json
{
"type": "module"
}
package.json sagt Node.js, dass die .js-Dateien Module sind; lassen Sie die Datei im Ordner.
Programm ausführen:
node main.jsPrüfungen ausführen (learnrun.js muss im selben Ordner liegen):
node --testlearnrun.js herunterladenÜbung 2 von 2
countdown(from, ms, log) protokolliert from, from - 1, ... bis 1, eine Zeile alle ms Millisekunden, dann 'liftoff'. Nutzen Sie setInterval aus node:timers/promises in einer for-await-Schleife und brechen Sie nach 1 mit break ab, damit das Intervall stoppt. watchdog(ms, onTimeout) gibt einen Timer zurück, der onTimeout nach ms aufruft, das Programm aber nicht am Laufen hält. Führen Sie node main.js aus, dann node --test.
Diese Übung braucht Node.js auf Ihrem Computer (die Browser-Version kann sie nicht ausführen). Dateien und Befehle stehen unten.
for await (const _ of setInterval(ms)) { ... } führt seinen Rumpf alle ms einmal aus.
Protokollieren Sie String(n), zählen Sie n herunter und brechen Sie bei 0 mit break ab; nach der Schleife protokollieren Sie 'liftoff'.
setTimeout(...).unref() gibt denselben Timeout zurück, also können Sie ihn direkt zurückgeben.
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
import {setInterval} from 'node:timers/promises';
// Logs from, from - 1, ..., 1, one line every ms milliseconds, then 'liftoff'.
export async function countdown(from, ms, log = console.log) {
let n = from;
for await (const _ of setInterval(ms)) {
log(String(n));
n--;
if (n === 0) break; // break stops the interval
}
log('liftoff');
}
// A timer that calls onTimeout after ms, but does not keep the program running.
export function watchdog(ms, onTimeout) {
return setTimeout(onTimeout, ms).unref();
}
const guard = watchdog(2000, () => console.log('too slow'));
await countdown(3, 100);
console.log('guard keeps the program alive:', guard.hasRef());
Installieren Sie Node.js 24 LTS oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.
main.js
import {setInterval} from 'node:timers/promises';
// Logs from, from - 1, ..., 1, one line every ms milliseconds, then 'liftoff'.
export async function countdown(from, ms, log = console.log) {
log('liftoff'); // no countdown yet
}
// A timer that calls onTimeout after ms, but does not keep the program running.
export function watchdog(ms, onTimeout) {
return setTimeout(onTimeout, ms);
}
const guard = watchdog(2000, () => console.log('too slow'));
await countdown(3, 100);
console.log('guard keeps the program alive:', guard.hasRef());
main.test.js
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {countdown, watchdog} from './main.js';
test('zählt von 3 herunter, dann liftoff', async () => {
const log = [];
await countdown(3, 10, (line) => log.push(line));
assert.deepEqual(log, ['3', '2', '1', 'liftoff'], `das Protokoll ist ${JSON.stringify(log)}`);
});
test('vor dem ersten Intervall wird nichts protokolliert', async () => {
const log = [];
const done = countdown(2, 30, (line) => log.push(line));
assert.deepEqual(log, [], `direkt nach dem Aufruf ist das Protokoll schon ${JSON.stringify(log)}`);
await done;
assert.deepEqual(log, ['2', '1', 'liftoff'], `das Protokoll ist ${JSON.stringify(log)}`);
});
test('der Watchdog hält das Programm nicht am Laufen', () => {
const timer = watchdog(10000, () => {});
const kept = timer.hasRef();
clearTimeout(timer);
assert.equal(kept, false, 'hasRef() sollte false sein: Rufen Sie unref() auf dem Timer auf');
});
test('der Watchdog feuert trotzdem, solange das Programm läuft', async () => {
const got = await new Promise((resolve) => {
watchdog(20, () => resolve('fired'));
setTimeout(resolve, 300, 'not fired');
});
assert.equal(got, 'fired', 'onTimeout sollte nach ms aufgerufen werden');
});
package.json
{
"type": "module"
}
package.json sagt Node.js, dass die .js-Dateien Module sind; lassen Sie die Datei im Ordner.
Programm ausführen:
node main.jsPrüfungen ausführen (learnrun.js muss im selben Ordner liegen):
node --testlearnrun.js herunterladenfunction save() {
console.log('saved');
}
setTimeout(save(), 100);
Was Node.js ausgibt
TypeError [ERR_INVALID_ARG_TYPE]: The "callback" argument must be of type function. Received undefinedWarum, und die Lösung
save() läuft sofort und gibt saved aus; setTimeout bekommt dann seinen Rückgabewert, undefined, statt einer Funktion und wirft. Übergeben Sie die Funktion selbst, setTimeout(save, 100), oder verpacken Sie den Aufruf: setTimeout(() => save(), 100).
console.log('waiting');
await setTimeout(100);
console.log('done');
Was Node.js ausgibt
TypeError [ERR_INVALID_ARG_TYPE]: The "callback" argument must be of type function. Received type number (100)Warum, und die Lösung
Ohne Import ist setTimeout die globale Callback-Version: Ihr erstes Argument muss eine Funktion sein, also wird 100 abgelehnt. Die Promise-Version steckt in node:timers/promises: import {setTimeout} from 'node:timers/promises'; dann wartet await setTimeout(100) 100 ms.
import {setTimeout} from 'node:timers/promises';
const later = setTimeout(5000, 'late');
later.unref();
console.log(await later);
Was Node.js ausgibt
TypeError: later.unref is not a functionWarum, und die Lösung
setTimeout aus node:timers/promises gibt ein Promise zurück, keinen Timeout, und ein Promise hat keine Methode unref. Übergeben Sie stattdessen die Option: setTimeout(5000, 'late', {ref: false}). Bedenken Sie: Steht sonst nichts aus, kann das Programm dann enden, bevor das Promise entschieden ist.
JavaScript im Browser: die eigene Engine Ihres Browsers in einem abgeschotteten Worker. Syntaxfehler findet acorn 8.18.0, MIT. Lizenz und Quellcode
5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.
Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.