Zum Inhalt springen
aviral gupta

// I1.5 · ca. 33 Min. · Aufbau

Eigene Funktionen schreiben, die ein Promise zurückgeben

Nach dieser Lektion verwandeln Sie einen Timer, eine Callback-API oder ein einmaliges Ereignis in eine Funktion, die ein Promise zurückgibt, sodass der restliche Code einfach darauf warten kann.

Lektion 5 von 6 in I1 Asynchrones JavaScript

Danach können Sie

  • Einen Timer oder eine Callback-API in new Promise packen und resolve oder reject im richtigen Moment aufrufen
  • Ein einmaliges Ereignis in ein Promise verwandeln und Promise.withResolvers nutzen, wenn resolve von außen gebraucht wird
  • Die fertigen Helfer nutzen: Promise.try überall, util.promisify und timers/promises in Node.js
  1. Aufwärmen · Aufgabe 1 von 7

    Zum Aufwärmen aus Lektion I1.2. Was gibt dieser Code aus?

    Promise.resolve(5)
      .then((n) => n * 2)
      .then((n) => console.log(n));
  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie es vorher, bevor Sie weiterlesen. In welcher Reihenfolge werden die vier Zeilen ausgegeben?

    console.log("1");
    const p = new Promise((resolve) => {
      console.log("2");
      resolve();
    });
    p.then(() => console.log("4"));
    console.log("3");
  3. Üben · Aufgabe 3 von 7

    Das alarm() aus MDN. Ergänzen Sie die Funktion, die das Promise bei einer negativen Verzögerung fehlschlagen lässt.

    function alarm(person, delay) {
      return new Promise((resolve, reject) => {
        if (delay < 0) {
          ____(new Error("negative delay"));
          return;
        }
        setTimeout(() => resolve(`Wake up, ${person}!`), delay);
      });
    }
    if (delay < 0) {(new Error("negative delay"));
  4. Üben · Aufgabe 4 von 7

    Ordnen Sie jedem Werkzeug zu, was es Ihnen liefert.

  5. Üben · Aufgabe 5 von 7

    Der Executor ruft drei Funktionen zum Erledigen auf. Was gibt dieser Code aus?

    const p = new Promise((resolve, reject) => {
      resolve("first");
      reject(new Error("second"));
      resolve("third");
    });
    p.then((v) => console.log("fulfilled:", v), (e) => console.log("rejected:", e.message));
  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. resolve bekommt ein Promise, das abgelehnt wird. Wie wird p erledigt?

    const p = new Promise((resolve) => {
      resolve(Promise.reject(new Error("inner")));
    });
  7. Anwenden · Aufgabe 7 von 7

    Kleine Aufgabe: Sie bekommen readSetting(name, callback), das nach 10 ms mit (null, value) oder bei einem unbekannten Namen mit (error) zurückruft. Schreiben Sie getSetting(name), das ein Promise zurückgibt. Warten Sie dann mit Promise.all gleichzeitig auf zwei Einstellungen und fangen Sie den Fehler eines Namens ab, den es nicht gibt.

    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

Ein Timer, ein Callback, ein Ereignis, dann die Helfer

Jeder Block verwandelt eine andere Art asynchroner Arbeit in ein Promise. wait() packt setTimeout ein. translate() packt einen Error-first-Callback ein: reject bei einem Fehler, resolve mit dem Ergebnis. once() macht den nächsten Klick zu einem Promise; der Listener wird vor dem Klick hinzugefügt. withResolvers lässt einen Timer resolve von außen aufrufen, und Promise.try macht aus einem synchronen throw eine Ablehnung.

main.js

// 1. A timer: wait(ms, value) fulfils with value after ms milliseconds.
const wait = (ms, value) => new Promise((resolve) => setTimeout(() => resolve(value), ms));

// 2. A callback API with error-first callbacks (a fake dictionary).
function lookUp(word, callback) {
  setTimeout(() => {
    if (word === "cat") callback(null, "Katze");
    else callback(new Error(`no entry for ${word}`));
  }, 10);
}
function translate(word) {
  return new Promise((resolve, reject) => {
    lookUp(word, (error, result) => {
      if (error) reject(error);
      else resolve(result);
    });
  });
}

// 3. A one-time event.
const once = (target, type) =>
  new Promise((resolve) => target.addEventListener(type, resolve, {once: true}));

console.log(await wait(10, "timer done"));
console.log("cat:", await translate("cat"));
try {
  await translate("dog");
} catch (error) {
  console.log("dog:", error.message);
}

const button = new EventTarget();
const clicked = once(button, "click"); // listen first
setTimeout(() => button.dispatchEvent(new Event("click")), 10);
console.log("event:", (await clicked).type);

// 4. withResolvers: resolve from outside an executor.
const {promise, resolve} = Promise.withResolvers();
setTimeout(() => resolve("resolved from outside"), 10);
console.log(await promise);

// 5. Promise.try: a function that may throw before it returns anything.
const parse = (text) => JSON.parse(text);
console.log("try:", await Promise.try(parse, "{bad").catch((error) => error.name));

Ausführen mit

node main.js

Ausgabe

timer done
cat: Katze
dog: no entry for dog
event: click
resolved from outside
try: SyntaxError
  • Jede Funktion hier gibt ein Promise zurück, das Programm benutzt also für alle await und try/catch auf dieselbe Weise.
  • Das Klick-Promise wurde erzeugt, bevor das Ereignis eintrat. Danach erzeugt, würde es nie erledigt.
  • JSON.parse warf sofort, und Promise.try machte daraus eine Ablehnung, die catch behandeln konnte.
Ä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

Eine Callback-API in ein Promise verwandeln

config.js hat readConfig(name, callback), das mit (null, value) zurückruft, oder bei einem unbekannten Namen mit einem Error. Schreiben Sie readConfigAsync(name) so, dass es ein Promise zurückgibt: erfüllt mit dem Wert oder abgelehnt mit dem Fehler von readConfig. Der Startcode gibt aus dem Callback etwas zurück, was dem Aufrufer nichts liefert.

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

    Ein return im Callback geht an readConfig zurück, nicht an Ihren Aufrufer. Was sollte readConfigAsync stattdessen zurückgeben?

  2. Hinweis 2

    Geben Sie new Promise((resolve, reject) => { … }) zurück und rufen Sie readConfig darin auf.

  3. Hinweis 3

    Im Callback: if (error) reject(error); else resolve(value);

Eine Lösung zeigen

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

import {readConfig} from "./config.js";

export function readConfigAsync(name) {
  return new Promise((resolve, reject) => {
    readConfig(name, (error, value) => {
      if (error) reject(error);
      else resolve(value);
    });
  });
}
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

import {readConfig} from "./config.js";

export function readConfigAsync(name) {
  return readConfig(name, (error, value) => value);
}

main.test.js

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

test('readConfigAsync gibt ein Promise zurück', () => {
  const result = readConfigAsync('theme');
  assert.ok(result instanceof Promise, `readConfigAsync gab ${result} zurück, kein Promise`);
});

test('Ein bekannter Name wird mit seinem Wert erfüllt', async () => {
  const got = await readConfigAsync('size');
  assert.equal(got, 14, `readConfigAsync('size') gab ${got}`);
});

test('Ein unbekannter Name wird mit dem Fehler des Callbacks abgelehnt', async () => {
  await assert.rejects(readConfigAsync('font'), {message: 'no config named font'}, 'readConfigAsync sollte bei einem unbekannten Namen ablehnen');
});

config.js

// A callback API: calls back with (error, value) after 5 ms.
const configs = {theme: "dark", size: 14};

export function readConfig(name, callback) {
  setTimeout(() => {
    if (name in configs) callback(null, configs[name]);
    else callback(new Error(`no config named ${name}`));
  }, 5);
}

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

Auf ein Ereignis warten, mit Zeitlimit

waitFor(target, type, ms) soll mit dem Ereignis erfüllt werden, wenn target innerhalb von ms Millisekunden type auslöst, und sonst mit einem Error abgelehnt werden, dessen Meldung no <type> within <ms> ms lautet. Der Timer im Startcode gibt den Error an resolve, ein fehlendes Ereignis sieht also wie ein Erfolg aus. Beheben Sie das.

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: Die Timeout-Fälle werden erfüllt. Welche der beiden Funktionen lässt ein Promise fehlschlagen?

  2. Hinweis 2

    resolve(new Error(…)) erfüllt mit einem Error-Objekt als Wert. Nur reject lässt das Promise ablehnen.

  3. Hinweis 3

    setTimeout(() => reject(new Error(`no ${type} within ${ms} ms`)), ms);

Eine Lösung zeigen

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

export function waitFor(target, type, ms) {
  return new Promise((resolve, reject) => {
    target.addEventListener(type, resolve, {once: true});
    setTimeout(() => reject(new Error(`no ${type} within ${ms} ms`)), ms);
  });
}
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 function waitFor(target, type, ms) {
  return new Promise((resolve, reject) => {
    target.addEventListener(type, resolve, {once: true});
    setTimeout(() => resolve(new Error(`no ${type} within ${ms} ms`)), ms);
  });
}

main.test.js

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

test('Ein Ereignis in der Zeit erfüllt mit dem Ereignis', async () => {
  const door = new EventTarget();
  const opened = waitFor(door, 'open', 50);
  setTimeout(() => door.dispatchEvent(new Event('open')), 5);
  const event = await opened;
  assert.equal(event.type, 'open', `waitFor wurde mit ${event} erfüllt`);
});

test('Kein Ereignis in der Zeit lehnt mit no open within 20 ms ab', async () => {
  const door = new EventTarget();
  await assert.rejects(waitFor(door, 'open', 20), {message: 'no open within 20 ms'}, 'waitFor sollte ablehnen, wenn das Ereignis nicht kommt');
});

test('Ein Ereignis nach dem Zeitlimit ändert das Ergebnis nicht', async () => {
  const door = new EventTarget();
  const late = waitFor(door, 'open', 10);
  setTimeout(() => door.dispatchEvent(new Event('open')), 30);
  await assert.rejects(late, {message: 'no open within 10 ms'}, 'das späte Ereignis sollte die Ablehnung nicht in einen Erfolg verwandeln');
});

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

Im Callback etwas zurückgeben, statt resolve aufzurufen

function wait(ms) {
  return new Promise(() => {
    setTimeout(() => {
      return "done";
    }, ms);
  });
}
console.log(await wait(10));

Was Node.js ausgibt

Warning: Detected unsettled top-level await

Warum, und die Lösung

Das return geht an den Timer zurück, der es ignoriert, und der Executor bekommt resolve gar nicht erst als Parameter. Das Promise bleibt für immer offen, das Top-Level-await wird also nie fertig, und Node.js beendet sich mit Exit-Code 13. Nehmen Sie resolve als Parameter des Executors und rufen Sie es auf: new Promise((resolve) => setTimeout(() => resolve("done"), ms)).

Ein throw in einem Callback im Executor

function checkedWait(ms) {
  return new Promise((resolve) => {
    setTimeout(() => {
      if (ms > 50) throw new Error("too long");
      resolve(ms);
    }, 0);
  });
}
checkedWait(100)
  .then((ms) => console.log("waited", ms))
  .catch((error) => console.log("caught:", error.message));

Was Node.js ausgibt

Error: too long

Warum, und die Lösung

Nur ein throw direkt im Executor lehnt das Promise ab. Dieses throw geschieht später, im Callback des Timers, nachdem der Executor zurückgekehrt ist, also sieht kein Promise es: Es ist ein nicht abgefangener Fehler, und das catch läuft nie. Melden Sie Fehler in Callbacks mit reject: Nehmen Sie reject als Parameter und rufen Sie reject(new Error("too long")); return auf.

Zwei Werte an resolve übergeben

const person = new Promise((resolve) => resolve("Ada", 36));
const [name, age] = await person;
console.log(name, age.toFixed(1));

Was Node.js ausgibt

TypeError: age.toFixed is not a function

Warum, und die Lösung

Ein Promise enthält genau einen Wert; resolve ignoriert jedes Argument nach dem ersten. person wird also mit dem Text "Ada" erfüllt, und das Destrukturieren eines Textes liefert seine Buchstaben: name ist "A", age ist "d". Packen Sie mehrere Werte in einen: resolve({name: "Ada", age: 36}) oder resolve(["Ada", 36]).

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

new Promise packt einen Callback ein

new Promise((resolve, reject) => { … }) führt seine Funktion, den Executor, sofort aus, noch vor der nächsten Zeile. Starten Sie darin die Arbeit und rufen Sie in ihrem Callback bei Erfolg resolve(value) und bei einem Fehler reject(error) auf. Der Rückgabewert des Executors wird ignoriert, ein return in einem Callback erledigt also nie etwas. Nur der erste Aufruf von resolve oder reject zählt; spätere bewirken nichts. Ein throw direkt im Executor lehnt das Promise ab, ein throw später, in einem Timer-Callback, ist dagegen ein nicht abgefangener Fehler.

Ereignisse und withResolvers

Ein Ereignis kann viele Male eintreten, ein Promise wird nur einmal erledigt. Übergeben Sie für ein einmaliges Ereignis resolve als Listener mit {once: true}, was den Listener nach dem ersten Aufruf entfernt. Fügen Sie den Listener hinzu, bevor das Ereignis eintreten kann: Ein Promise sieht ein Ereignis nicht, das schon geschehen ist. Muss resolve von außerhalb des Executors aufgerufen werden, etwa aus einer anderen Funktion, liefert Promise.withResolvers() ein Objekt mit promise, resolve und reject.

Fertige Helfer

Promise.try(fn, ...args) ruft fn sofort auf und liefert immer ein Promise: erfüllt mit dem Wert oder abgelehnt, wenn fn wirft, auch synchron. Bei Promise.resolve(fn()) entkäme ein synchrones throw. In Node.js macht util.promisify(fn) aus einer Funktion mit Error-first-Callback (error, value) eine, die ein Promise zurückgibt, und node:timers/promises hat ein setTimeout(ms, value), das ein Promise zurückgibt. Im Browser schreiben Sie solche Wrapper selbst.

Quellen

Zuletzt geprüft am 5. Oktober 2026