Zum Inhalt springen
aviral gupta

// I1.1 · ca. 32 Min. · Aufbau

Die Event Loop und Promises: asynchrones JavaScript vorhersagen

Nach dieser Lektion können Sie vorhersagen, in welcher Reihenfolge synchroner Code, Promise-Callbacks, Fortsetzungen nach await und Timer laufen, asynchrone Fehler dort abfangen, wo sie wirklich entstehen, und gezielt zwischen Promise.all und Promise.allSettled wählen.

Lektion 1 von 6 in I1 Asynchrones JavaScript

Anfang des Moduls

Danach können Sie

  • Die Konsolenausgabe von Code ordnen, der synchrone Aufrufe, Microtasks und setTimeout mischt
  • Ein abgelehntes Promise mit try/catch behandeln, indem Sie es an der richtigen Stelle abwarten
  • Erklären, wann Promise.all ablehnt und was Promise.allSettled stattdessen liefert
  1. Aufwärmen · Aufgabe 1 von 7

    Zum Aufwärmen: einfacher synchroner Code, noch nichts Asynchrones. Was gibt dieser Code aus?

    function a() {
      console.log('a start');
      b();
      console.log('a end');
    }
    function b() {
      console.log('b');
    }
    a();
  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie es vorher, bevor Sie weiterlesen. Was gibt dieser Code der Reihe nach aus?

    console.log('A');
    setTimeout(() => console.log('B'), 0);
    Promise.resolve().then(() => console.log('C'));
    console.log('D');
  3. Üben · Aufgabe 3 von 7

    Bringen Sie die Zeilen, die dieses Programm ausgibt, in die Reihenfolge, in der sie erscheinen.

    async function run() {
      console.log('run: start');
      await null;
      console.log('run: after await');
    }
    
    setTimeout(() => console.log('timeout'), 0);
    run();
    queueMicrotask(() => console.log('microtask'));
    console.log('script end');
    1. 1.timeout
    2. 2.script end
    3. 3.run: after await
    4. 4.microtask
    5. 5.run: start
  4. Üben · Aufgabe 4 von 7

    Fehlersuche. Dies ist ein CommonJS-Skript, ausgeführt mit Node.js. Warum wird „caught:“ nie ausgegeben, und warum beendet sich der Prozess nach „done“ mit „Error: boom“? (Die Antworten bleiben englisch, weil der Test sie mit einer Aussage im Programm abgleicht.)

    async function load() {
      throw new Error('boom');
    }
    
    try {
      load();
    } catch (err) {
      console.log('caught:', err.message);
    }
    console.log('done');
  5. Üben · Aufgabe 5 von 7

    Ihre Seite zeigt den Namen eines Benutzers und seine Beiträge. Ist die Beitrags-API ausgefallen, wollen Sie trotzdem den Namen. Ergänzen Sie die Promise-Methode, sodass „ok: Ada“ und dann „failed: posts API down“ ausgegeben wird.

    // ES module, so top-level await is allowed
    const fetchUser = () => Promise.resolve('Ada');
    const fetchPosts = () => Promise.reject(new Error('posts API down'));
    
    const results = await Promise.____([fetchUser(), fetchPosts()]);
    for (const r of results) {
      if (r.status === 'fulfilled') console.log('ok:', r.value);
      else console.log('failed:', r.reason.message);
    }
    await Promise.([fetchUser(), fetchPosts()])
  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. load() packt den Aufruf in try/catch und gibt bei einem Fehler einen Ersatzwert zurück. Was gibt dieser Code aus?

    async function getData() {
      throw new Error('offline');
    }
    
    async function load() {
      try {
        return getData();
      } catch (err) {
        return 'fallback';
      }
    }
    
    load().then(
      (value) => console.log('value:', value),
      (err) => console.log('error:', err.message)
    );
  7. Anwenden · Aufgabe 7 von 7

    Kleine Aufgabe. Schreiben Sie loadDashboard() in Node.js. Das Benutzerprofil ist erforderlich: Schlägt es fehl, werfen Sie einen Fehler mit einer klaren Meldung. Drei Widgets (news, weather, stocks) sind optional: Zeigen Sie die, die gelingen, und protokollieren Sie die, die fehlschlagen. Alles soll gleichzeitig starten, nicht nacheinander. Täuschen Sie das Netzwerk mit einer Hilfsfunktion delay(ms, value, fail) auf Basis von setTimeout vor, lassen Sie ein Widget fehlschlagen und führen Sie es aus.

    Prüfen Sie Ihr Ergebnis anhand dieser Liste

Selbst programmieren

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

Jede Art von Job in einem Programm

Die Buchstaben zeigen die Reihenfolge, in der die Zeilen erscheinen. Zuerst laufen die synchronen Zeilen, A bis C, einschließlich des Starts von work() bis zu seinem await. Dann wird die Microtask-Schlange abgearbeitet: der then()-Callback, der Rest von work() und alles, worauf die Top-Level-await warten, denn ein Promise, das ohne Timer erledigt wird, wird in Microtasks erledigt. Der Task des Timers läuft zuletzt, obwohl er als Zweites eingeplant wurde.

main.js

console.log('A sync');
setTimeout(() => console.log('H timer (a task)'), 0);
Promise.resolve().then(() => console.log('D then (a microtask)'));

async function work() {
  console.log('B work starts (still sync)');
  await null;
  console.log('E work after await (a microtask)');
}
work();
console.log('C sync, end of the first job');

// Errors: await inside try, and allSettled to keep every outcome.
const fail = (message) => Promise.reject(new Error(message));
async function load() {
  try {
    return await fail('offline');
  } catch (err) {
    return `fallback (${err.message})`;
  }
}
console.log('F load:', await load());
const results = await Promise.allSettled([Promise.resolve('news'), fail('weather down')]);
console.log('G allSettled:', results.map((r) => (r.status === 'fulfilled' ? r.value : r.reason.message)));

Ausführen mit

node main.js

Ausgabe

A sync
B work starts (still sync)
C sync, end of the first job
D then (a microtask)
E work after await (a microtask)
F load: fallback (offline)
G allSettled: [ 'news', 'weather down' ]
H timer (a task)
  • Ein Timer mit 0 ms heißt nicht „jetzt“: Sein Callback ist ein Task, und jeder wartende Microtask läuft vor dem nächsten Task.
  • load() fängt die Ablehnung ab, weil es innerhalb des try abwartet: return await, nicht return.
  • allSettled behielt beide Ausgänge in der Reihenfolge der Eingabe; Promise.all wäre mit „weather down“ abgelehnt worden.
Ändern und ausführen

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.

Übungen

Übung 1 von 2

Die Ablehnung dort abfangen, wo sie entsteht

safeLoad(load) ruft die async-Funktion load auf und soll mit deren Wert erfüllt werden, oder mit "fallback", wenn load abgelehnt wird. Der Startcode gibt load() innerhalb des try zurück, ohne es abzuwarten, die Ablehnung rutscht also am catch vorbei. Beheben Sie das mit einem Wort.

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.

Hinweise
  1. Hinweis 1

    Führen Sie die Tests aus: safeLoad wird mit offline abgelehnt. Wann wird das Promise von load() abgelehnt: innerhalb des try oder erst, nachdem safeLoad es verlassen hat?

  2. Hinweis 2

    Das catch sieht nur eine Ablehnung, die geworfen wird, solange das try noch läuft. Welches Schlüsselwort macht aus einer Ablehnung ein throw?

  3. Hinweis 3

    return await load();

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

export async function safeLoad(load) {
  try {
    return await load();
  } catch {
    return "fallback";
  }
}
Auf dem eigenen Computer ausführen

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

export async function safeLoad(load) {
  try {
    return load();
  } catch {
    return "fallback";
  }
}

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {safeLoad} from './main.js';

test('Ein load, das gelingt, liefert seinen Wert', async () => {
  const got = await safeLoad(async () => 'data');
  assert.equal(got, 'data', `safeLoad gab ${got}`);
});

test('Ein load, das abgelehnt wird, liefert fallback', async () => {
  const got = await safeLoad(async () => {
    throw new Error('offline');
  }).catch((error) => `abgelehnt mit ${error.message}`);
  assert.equal(got, 'fallback', `safeLoad gab: ${got}`);
});

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.js

Prüfungen ausführen (learnrun.js muss im selben Ordner liegen):

node --test
learnrun.js herunterladen

Übung 2 von 2

Jeden Ausgang melden

summarize(promises) soll auf jedes Promise warten und mit {ok, failed} erfüllt werden: ok enthält die Werte derer, die erfüllt wurden, failed die Meldungen derer, die abgelehnt wurden, beides in der Reihenfolge der Eingabe. Der Startcode benutzt Promise.all, ein einziger Fehlschlag lehnt also alles ab. Benutzen Sie den Kombinator, der nie ablehnt.

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.

Hinweise
  1. Hinweis 1

    Führen Sie die Tests aus: summarize wird mit weather down abgelehnt. Welcher Kombinator wartet auf jede Eingabe und lehnt nie ab?

  2. Hinweis 2

    Promise.allSettled liefert pro Eingabe ein Objekt: {status: "fulfilled", value} oder {status: "rejected", reason}.

  3. Hinweis 3

    Gehen Sie die Ergebnisse durch: result.value an ok anhängen oder result.reason.message an failed.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

export async function summarize(promises) {
  const results = await Promise.allSettled(promises);
  const ok = [];
  const failed = [];
  for (const result of results) {
    if (result.status === "fulfilled") ok.push(result.value);
    else failed.push(result.reason.message);
  }
  return {ok, failed};
}
Auf dem eigenen Computer ausführen

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

export async function summarize(promises) {
  const values = await Promise.all(promises);
  return {ok: values, failed: []};
}

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {summarize} from './main.js';

const fail = (message) => Promise.reject(new Error(message));

test('Alles gelingt: jeder Wert in ok', async () => {
  const got = await summarize([Promise.resolve('news'), Promise.resolve('stocks')]);
  assert.deepEqual(got, {ok: ['news', 'stocks'], failed: []}, `summarize gab ${JSON.stringify(got)}`);
});

test('Fehlschläge werden gemeldet, der Rest bleibt erhalten', async () => {
  const got = await summarize([Promise.resolve('news'), fail('weather down'), fail('stocks down')]).catch((error) => `abgelehnt mit ${error.message}`);
  assert.deepEqual(got, {ok: ['news'], failed: ['weather down', 'stocks down']}, `summarize gab ${JSON.stringify(got)}`);
});

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.js

Prüfungen ausführen (learnrun.js muss im selben Ordner liegen):

node --test
learnrun.js herunterladen

Häufige Fehler

Erwarten, dass ein Timer mit 0 ms vor der nächsten Zeile läuft

let data;
setTimeout(() => {
  data = {items: [1, 2]};
}, 0);
console.log(data.items.length);

Was Node.js ausgibt

TypeError: Cannot read properties of undefined (reading 'items')

Warum, und die Lösung

Ein Timer-Callback ist ein Task: Er läuft erst, nachdem das aktuelle Skript beendet ist, auch bei 0 ms Verzögerung. Wenn die letzte Zeile läuft, ist data noch undefined. Benutzen Sie die Daten innerhalb des Callbacks, oder packen Sie den Timer in ein Promise und warten Sie darauf, bevor Sie data lesen.

try/catch um einen async-Aufruf ohne await

async function load() {
  throw new Error("boom");
}
try {
  load();
} catch (err) {
  console.log("caught:", err.message);
}
console.log("done");

Was Node.js ausgibt

Error: boom

Warum, und die Lösung

load() wirft nicht beim Aufruf: Es gibt ein abgelehntes Promise zurück, das try endet normal, „done“ erscheint, und die Ablehnung wird nie behandelt, das Programm stoppt also mit ihr. Warten Sie innerhalb des try darauf, in einer async-Funktion oder einem Modul: try { await load(); } catch (err) { … }.

Promise.all mit einer fehlschlagenden Eingabe und ohne catch

const results = await Promise.all([Promise.resolve(1), Promise.reject(new Error("x failed"))]);
console.log(results);

Was Node.js ausgibt

Error: x failed

Warum, und die Lösung

Promise.all lehnt mit der ersten Ablehnung ab, und ein await auf ein abgelehntes Promise wirft. Nichts fängt das hier ab, das Programm stoppt also vor console.log. Packen Sie das await in try/catch, oder benutzen Sie Promise.allSettled, wenn jeder Ausgang zählt.

JavaScript im Browser: die eigene Engine Ihres Browsers in einem abgeschotteten Worker. Syntaxfehler findet acorn 8.18.0, MIT. Lizenz und Quellcode

Abschlussquiz

5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.

Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.

Problem melden

Etwas ist falsch oder unklar? Beschreiben Sie es kurz, dann wird es geprüft und korrigiert.

#

Mindestens 20 Zeichen.

Nur, wenn Sie eine Antwort wünschen.

Kernideen

Ein Job nach dem anderen, bis zum Ende

JavaScript führt auf dem Call Stack immer einen Job nach dem anderen aus. Das Ausführungsmodell von MDN nennt das Run-to-Completion: Sobald eine Funktion startet, läuft nichts anderes, bis der Job fertig ist. Asynchrone Arbeit unterbricht Ihren Code nie. Ihr Ergebnis kommt später als Callback an, etwa als then()-Handler oder als setTimeout()-Funktion, und wird als neuer Job eingereiht.

Microtasks überholen die Schlange

Jobs gibt es in zwei Arten. Promise-Callbacks, queueMicrotask()-Callbacks und der Code nach einem await sind Microtasks. setTimeout()-Callbacks sind Tasks. Wenn der aktuelle Job endet, wird die ganze Microtask-Schlange abgearbeitet, auch Microtasks, die unterwegs dazukommen, bevor der nächste Task läuft. Deshalb schlägt der then()-Handler eines erfüllten Promise setTimeout(…, 0), und deshalb läuft Code nach await nie synchron, selbst wenn Sie einen einfachen Wert wie 42 abwarten.

Fehler wandern mit dem Promise

Eine async-Funktion gibt immer ein Promise zurück; wirft sie, wird dieses Promise abgelehnt. Ein try/catch sieht die Ablehnung nur, wenn Sie das Promise innerhalb des try abwarten, und das schließt return await ein. Promise.all lehnt mit dem Grund der ersten Ablehnung ab und bricht die anderen Operationen nicht ab. Promise.allSettled lehnt nie ab: Es wird mit je einem Objekt {status, value} oder {status, reason} pro Eingabe erfüllt, in der Reihenfolge der Eingabe.

Quellen

Zuletzt geprüft am 28. September 2026