Aufwärmen · Aufgabe 1 von 7
Zum Aufwärmen aus Lektion I1.1: ein Callback für setTimeout. Was gibt dieser Code aus?
console.log("order");
setTimeout(() => console.log("ready"), 0);
console.log("wait");// I1.2 · ca. 32 Min. · Aufbau
Nach dieser Lektion machen Sie aus verschachtelten Callbacks eine flache Promise-Kette, die auf jeden Schritt wartet, alle Fehler in einem catch() behandelt und in finally() aufräumt.
Danach können Sie
Aufwärmen · Aufgabe 1 von 7
console.log("order");
setTimeout(() => console.log("ready"), 0);
console.log("wait");Vorhersagen · Aufgabe 2 von 7
const first = Promise.resolve(2);
const second = first.then((n) => n * 10);
console.log(first === second);
second.then((n) => console.log(n));Üben · Aufgabe 3 von 7
Promise.reject(new Error("timeout"))
.then((data) => console.log("data:", data))
.____((error) => console.log("failed:", error.message));Üben · Aufgabe 4 von 7
Promise.resolve()
.then(() => console.log("step 1"))
.then(() => {
throw new Error("step 2 broke");
})
.then(() => console.log("step 3"))
.catch((error) => console.log("caught:", error.message))
.then(() => console.log("after the catch"))
.finally(() => console.log("cleanup"));Üben · Aufgabe 5 von 7
const later = (value) => new Promise((resolve) => setTimeout(() => resolve(value), 10));
Promise.resolve("id-7")
.then((id) => later(`user for ${id}`))
.then((result) => console.log(typeof result, result));Denksport · Aufgabe 6 von 7
const save = (x) => new Promise((resolve) => setTimeout(() => resolve(`saved ${x}`), 10));
Promise.resolve("draft")
.then((doc) => {
save(doc);
})
.then((result) => console.log("result:", result));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
findUser und findOrders geben jeweils ein Promise zurück, das etwas später abgeschlossen wird. Das Programm zeigt, dass der Aufruf sofort ein Promise zurückgibt, und verkettet dann die Schritte: Jedes then() erhält den Wert des vorigen Schritts, das erste gibt das nächste Promise zurück, damit die Kette darauf wartet, und catch() und finally() schließen die Kette ab. Die zweite Hälfte fragt nach Benutzer 2, den es nicht gibt, also überspringt die Ablehnung ein then() und erreicht das letzte catch().
main.js
// Two steps that finish later. Each returns a promise (lesson I1.5 shows how to make one).
function findUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => (id === 1 ? resolve({id, name: "Ada"}) : reject(new Error(`no user ${id}`))), 20);
});
}
function findOrders(user) {
return new Promise((resolve) => setTimeout(() => resolve([`${user.name}-A`, `${user.name}-B`]), 10));
}
const pending = findUser(1);
console.log("right after the call:", pending instanceof Promise);
pending
.then((user) => {
console.log("user:", user.name);
return findOrders(user); // return the next promise, so the chain waits for it
})
.then((orders) => {
console.log("orders:", orders);
return orders.length;
})
.then((count) => console.log("count:", count))
.catch((error) => console.log("failed:", error.message))
.finally(() => console.log("done with user 1"))
.then(() => findUser(2))
.then((user) => console.log("never printed:", user.name))
.catch((error) => console.log("failed:", error.message));
Ausführen mit
node main.jsAusgabe
right after the call: true
user: Ada
orders: [ 'Ada-A', 'Ada-B' ]
count: 2
done with user 1
failed: no user 2Tab 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.
Übung 1 von 2
orderTotal(fetchPrice, fetchShipping) soll ein Promise von Preis plus Versand zurückgeben. fetchPrice() gibt ein Promise des Preises zurück; fetchShipping(price) ein Promise der Versandkosten für diesen Preis. Der Starter ruft beide auf, aber sein Promise wird mit undefined erfüllt. Sorgen Sie dafür, dass die Kette auf den Versand wartet und die Summe zurückgibt. Eine Ablehnung von fetchPrice() muss orderTotal weiterhin ablehnen.
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.
Führen Sie die Tests aus: Die Summe ist undefined. Was gibt der äußere Callback zurück?
Der Callback hat geschweifte Klammern, also gibt er nur zurück, was eine return-Anweisung ihm gibt.
Schreiben Sie return vor fetchShipping(price).then(…), damit das äußere then() auf dieses Promise wartet.
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
// Resolve to price + shipping.
export function orderTotal(fetchPrice, fetchShipping) {
return fetchPrice().then((price) => {
return fetchShipping(price).then((shipping) => price + shipping);
});
}
Installieren Sie ECMAScript 2026 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.
main.js
// Resolve to price + shipping.
export function orderTotal(fetchPrice, fetchShipping) {
return fetchPrice().then((price) => {
fetchShipping(price).then((shipping) => price + shipping);
});
}
main.test.js
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {orderTotal} from './main.js';
const later = (value) => new Promise((resolve) => setTimeout(() => resolve(value), 5));
const shipping = (price) => later(price >= 50 ? 0 : 4.5);
test('A price of 20 with 4.50 shipping gives 24.5', async () => {
const got = await orderTotal(() => later(20), shipping);
assert.equal(got, 24.5, `orderTotal gave ${got}, expected 24.5`);
});
test('A price of 60 ships free, so the total is 60', async () => {
const got = await orderTotal(() => later(60), shipping);
assert.equal(got, 60, `orderTotal gave ${got}, expected 60`);
});
test('A rejected price rejects the total with the same error', async () => {
const failing = () => Promise.reject(new Error('price service down'));
await assert.rejects(orderTotal(failing, shipping), {message: 'price service down'}, 'orderTotal should reject with the price error');
});
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
loadName(fetchUser, log) soll ein Promise des Benutzernamens in Großbuchstaben zurückgeben, oder GUEST, wenn fetchUser() abgelehnt wird oder der Benutzer keinen Namen hat. In jedem Fall muss es log("loaded") genau einmal aufrufen. Der Starter übergibt einen Fehler-Handler als zweites Argument von then(), der keine Fehler aus dem ersten sieht, und er loggt nur bei Erfolg. Nutzen Sie catch() und finally().
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.
Führen Sie die Tests aus: Der Benutzer ohne Namen scheitert mit einem TypeError. Welcher Handler sollte ihn sehen?
then(onOk, onFail): onFail behandelt nur das Promise, auf dem then() aufgerufen wurde, nicht Fehler, die in onOk geworfen werden.
Hängen Sie .catch(() => "GUEST") hinter das then() und setzen Sie log("loaded") in .finally(…), das auf beiden Wegen läuft.
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
// Resolve to the name in capitals, or GUEST; call log("loaded") once either way.
export function loadName(fetchUser, log) {
return fetchUser()
.then((user) => user.name.toUpperCase())
.catch(() => "GUEST")
.finally(() => log("loaded"));
}
Installieren Sie ECMAScript 2026 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.
main.js
// Resolve to the name in capitals, or GUEST; call log("loaded") once either way.
export function loadName(fetchUser, log) {
return fetchUser()
.then((user) => user.name.toUpperCase(), () => "GUEST")
.then((name) => {
log("loaded");
return name;
});
}
main.test.js
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {loadName} from './main.js';
const run = async (fetchUser) => {
const lines = [];
const name = await loadName(fetchUser, (line) => lines.push(line));
return {name, lines};
};
test('A user named ada gives ADA and logs loaded once', async () => {
const {name, lines} = await run(() => Promise.resolve({name: 'ada'}));
assert.equal(name, 'ADA', `the name was ${name}, expected ADA`);
assert.deepEqual(lines, ['loaded'], `log got ${JSON.stringify(lines)}, expected ["loaded"]`);
});
test('A rejected fetchUser gives GUEST and logs loaded once', async () => {
const {name, lines} = await run(() => Promise.reject(new Error('offline')));
assert.equal(name, 'GUEST', `the name was ${name}, expected GUEST`);
assert.deepEqual(lines, ['loaded'], `log got ${JSON.stringify(lines)}, expected ["loaded"]`);
});
test('A user without a name gives GUEST and logs loaded once', async () => {
const {name, lines} = await run(() => Promise.resolve({}));
assert.equal(name, 'GUEST', `the name was ${name}, expected GUEST`);
assert.deepEqual(lines, ['loaded'], `log got ${JSON.stringify(lines)}, expected ["loaded"]`);
});
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 herunterladenconst findUser = (id) => Promise.resolve({id, name: "Ada"});
const user = findUser(1);
console.log(user.name.toUpperCase());
Was Node.js ausgibt
TypeError: Cannot read properties of undefined (reading 'toUpperCase')Warum, und die Lösung
findUser gibt ein Promise zurück, nicht den Benutzer. Ein Promise hat keine Eigenschaft name, also ist user.name undefined. Den Wert gibt es erst in einem then()-Callback (oder nach await, Lektion I1.3): findUser(1).then((user) => console.log(user.name.toUpperCase())).
Promise.resolve([3, 4])
.then((items) => {
items.map((n) => n * 2);
})
.then((doubled) => console.log(doubled.length));
Was Node.js ausgibt
TypeError: Cannot read properties of undefined (reading 'length')Warum, und die Lösung
Ein Callback mit geschweiften Klammern gibt undefined zurück, solange er nicht return sagt, also erhält der nächste Schritt undefined. Nichts fängt den TypeError ab, also stoppt der Lauf damit. Schreiben Sie return items.map((n) => n * 2); oder lassen Sie die Klammern weg. Dieselbe Regel lässt eine Kette warten: Geben Sie das nächste Promise zurück.
const load = () => Promise.reject(new Error("offline"));
load().then((data) => console.log("data:", data));
console.log("started");
Was Node.js ausgibt
Error: offlineWarum, und die Lösung
Ohne catch() kann eine Ablehnung nirgendwohin. Node.js meldet eine unbehandelte Ablehnung und stoppt mit Exit-Code 1, nachdem „started“ ausgegeben wurde; ein Browser schreibt sie in die Konsole. Beenden Sie jede Kette mit catch() oder geben Sie die Kette an einen Aufrufer zurück, der sie behandelt: load().then(…).catch((error) => console.log("failed:", error.message)).
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.