Zum Inhalt springen
aviral gupta

// I6.2 · ca. 40 Min. · Aufbau

HTTP-Fehler, Wiederholungen und Timeouts

Nach dieser Lektion scheitert Ihr fetch-Code eindeutig: Fehlerstatus werden zu Fehlern, vorübergehende Ausfälle werden einige Male wiederholt, und langsame Anfragen enden nach einem Timeout.

Lektion 2 von 5 in I6 Netzwerkanfragen und Speicher im Client

Danach können Sie

  • response.ok prüfen und einen HttpError mit dem Status werfen, und einen 404 anders behandeln als einen 500
  • Eine Anfrage einige Male mit wachsender Pause wiederholen, nur nach einem Netzwerkfehler oder einem 5xx-Status
  • Eine langsame Anfrage mit AbortSignal.timeout stoppen und TimeoutError sowie einen Body erkennen, der kein JSON ist
  1. Aufwärmen · Aufgabe 1 von 7

    Aufwärmen aus Lektion I4.2. Was gibt das aus?

    class HttpError extends Error {
      constructor(status) {
        super("HTTP " + status);
        this.name = "HttpError";
        this.status = status;
      }
    }
    const error = new HttpError(404);
    console.log(error instanceof Error, error.name, error.status);
  2. Vorhersagen · Aufgabe 2 von 7

    Raten Sie, bevor Sie weiterlesen. /broken antwortet mit 500 und dem Text Server error. Was gibt das aus?

    async function getJson(url) {
      const response = await fetch(url);
      return response.json();
    }
    try {
      console.log(await getJson(api + "/broken"));
    } catch (error) {
      console.log(error.name);
    }
  3. Üben · Aufgabe 3 von 7

    Ergänzen Sie die Eigenschaft, die dem HttpError die Nummer des Status gibt, etwa 404.

    if (!response.ok) throw new HttpError(response.____);
    throw new HttpError(response.);
  4. Üben · Aufgabe 4 von 7

    Ordnen Sie jedem Ausfall zu, was Ihr Code tun soll.

  5. Üben · Aufgabe 5 von 7

    /flaky antwortet zweimal mit 503 und dann mit 200. Was gibt das aus?

    for (let attempt = 1; attempt <= 3; attempt++) {
      const response = await fetch(api + "/flaky");
      console.log(attempt, response.status);
      if (response.status < 500) break;
    }
  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. /slow antwortet nach 300 ms. Was gibt das aus?

    try {
      await fetch(api + "/slow", {signal: AbortSignal.timeout(50)});
      console.log("done");
    } catch (error) {
      console.log(error.name, error instanceof Error);
    }
  7. Anwenden · Aufgabe 7 von 7

    Mini-Aufgabe: Schreiben Sie mit server.js aus dem durchgespielten Beispiel eine Klasse HttpError und getJson(url): Sie gibt nach einer Sekunde auf, wirft für einen Status, der nicht ok ist, einen HttpError und gibt das geparste JSON zurück. Laden Sie /books/1, /books/9 und /broken: Geben Sie den Titel aus, "not found" für einen 404 und "please try again later" für alles andere.

    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 sorgfältiges getJson mit Wiederholungen

server.js ist ein kleiner Testserver mit einem vorhandenen Buch, einem fehlenden, einer kaputten Route (500), einer ausgelasteten Route, die zweimal 503 antwortet, einer HTML-Seite und einer langsamen Route. getJson macht aus Fehlerstatus HttpErrors und gibt nach einem Timeout auf. getJsonWithRetry versucht es nur nach einem Netzwerkfehler oder einem 5xx-Status erneut, höchstens dreimal, und wartet jedes Mal länger.

main.js

import {startServer} from "./server.js";

class HttpError extends Error {
  constructor(status) {
    super(`HTTP ${status}`);
    this.name = "HttpError";
    this.status = status;
  }
}

async function getJson(url, ms = 1000) {
  const response = await fetch(url, {signal: AbortSignal.timeout(ms)});
  if (!response.ok) throw new HttpError(response.status);
  return response.json(); // can still reject with a SyntaxError
}

const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function getJsonWithRetry(url, attempts = 3) {
  for (let attempt = 1; ; attempt++) {
    try {
      return await getJson(url);
    } catch (error) {
      const temporary = error instanceof TypeError || (error instanceof HttpError && error.status >= 500);
      console.log(`  attempt ${attempt}: ${error.message}`);
      if (!temporary || attempt === attempts) throw error;
      await wait(50 * 2 ** (attempt - 1)); // 50 ms, then 100 ms, …
    }
  }
}

const server = await startServer();
const api = server.url;
for (const path of ["/books/1", "/books/9", "/broken", "/html"]) {
  try {
    console.log(path, (await getJson(api + path)).title);
  } catch (error) {
    console.log(path, error.name + ": " + error.message);
  }
}
try {
  await getJson(api + "/slow", 50);
} catch (error) {
  console.log("/slow", error.name);
}

console.log("/flaky", (await getJsonWithRetry(api + "/flaky")).title);
try {
  await getJsonWithRetry(api + "/books/9");
} catch (error) {
  console.log("gave up:", error.message);
}
console.log("requests to /flaky:", server.count("/flaky"));
await server.close();

server.js

// A small test server for this lesson, made with node:http. You do not need to change it.
import {createServer} from "node:http";

export async function startServer() {
  const counts = {}; // how often each path was requested
  const server = createServer((request, response) => {
    const path = request.url;
    counts[path] = (counts[path] ?? 0) + 1;
    const send = (status, type, text) => {
      response.writeHead(status, {"Content-Type": type});
      response.end(text);
    };
    const json = (status, value) => send(status, "application/json", JSON.stringify(value));
    if (path === "/books/1") json(200, {id: 1, title: "Dune"});
    else if (path === "/broken") send(500, "text/plain", "Server error");
    else if (path === "/flaky") {
      // busy twice (503), then it works
      if (counts[path] <= 2) send(503, "text/plain", "Busy");
      else json(200, {id: 2, title: "Emma"});
    } else if (path === "/html") send(200, "text/html", "<h1>Down for maintenance</h1>");
    else if (path === "/slow") {
      // answers after 300 ms, unless the client gave up
      const timer = setTimeout(() => json(200, {id: 3, title: "Kindred"}), 300);
      response.on("close", () => clearTimeout(timer));
    } else json(404, {error: "Not found"});
  });
  await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); // port 0: any free port
  return {
    url: `http://127.0.0.1:${server.address().port}`,
    count: (path) => counts[path] ?? 0,
    close: () => new Promise((resolve) => server.close(resolve))
  };
}

Ausführen mit

node main.js

Ausgabe

/books/1 Dune
/books/9 HttpError: HTTP 404
/broken HttpError: HTTP 500
/html SyntaxError: Unexpected token '<', "<h1>Down f"... is not valid JSON
/slow TimeoutError
  attempt 1: HTTP 503
  attempt 2: HTTP 503
/flaky Emma
  attempt 1: HTTP 404
gave up: HTTP 404
requests to /flaky: 3
  • /html war ok (Status 200), aber sein Body war HTML, daher wurde json() mit einem SyntaxError abgelehnt.
  • Die ausgelastete Route wurde dreimal versucht und klappte beim dritten Mal; der 404 wurde nur einmal versucht.
  • Das Timeout wurde nach 50 ms mit TimeoutError abgelehnt, lange bevor die langsame Antwort gekommen wäre.

Übungen

Übung 1 von 3

Fehlerstatus werden zu HttpErrors

getJson(url) soll das geparste JSON einer ok-Response zurückgeben und für jeden anderen Status mit einem HttpError abgelehnt werden (die Klasse ist vorgegeben), mit dem Status in error.status. Der Starter parst jede Antwort, daher liefert ein 404 das Fehlerobjekt des Servers wie Daten, und ein 500 einen SyntaxError.

Diese Übung braucht Node.js auf Ihrem Computer (die Browser-Version kann sie nicht ausführen). Dateien und Befehle stehen unten.

Hinweise
  1. Hinweis 1

    Führen Sie die Tests aus: Für /books/9 gibt getJson gar keinen Fehler. Welche Eigenschaft der Response sagt, dass etwas schiefging?

  2. Hinweis 2

    Prüfen Sie response.ok, bevor Sie den Body lesen.

  3. Hinweis 3

    if (!response.ok) throw new HttpError(response.status);

Eine Lösung zeigen

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

export class HttpError extends Error {
  constructor(status) {
    super(`HTTP ${status}`);
    this.name = "HttpError";
    this.status = status;
  }
}

export async function getJson(url) {
  const response = await fetch(url);
  if (!response.ok) throw new HttpError(response.status);
  return response.json();
}
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 class HttpError extends Error {
  constructor(status) {
    super(`HTTP ${status}`);
    this.name = "HttpError";
    this.status = status;
  }
}

export async function getJson(url) {
  const response = await fetch(url);
  return response.json();
}

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {startServer} from './server.js';
import {HttpError, getJson} from './main.js';

const caught = (promise) => promise.then(() => null, (error) => error);

test('getJson gives book 1', async () => {
  const server = await startServer();
  try {
    const book = await getJson(server.url + '/books/1');
    assert.deepEqual(book, {id: 1, title: 'Dune'}, `getJson gave ${JSON.stringify(book)}`);
  } finally {
    await server.close();
  }
});

test('A 404 rejects with an HttpError with status 404', async () => {
  const server = await startServer();
  try {
    const error = await caught(getJson(server.url + '/books/9'));
    assert.ok(error instanceof HttpError, `getJson should reject with an HttpError, but it gave ${error ? error.name : 'no error'}`);
    assert.equal(error.status, 404, `error.status is ${error.status}`);
  } finally {
    await server.close();
  }
});

test('A 500 rejects with an HttpError with status 500', async () => {
  const server = await startServer();
  try {
    const error = await caught(getJson(server.url + '/broken'));
    assert.ok(error instanceof HttpError, `getJson should reject with an HttpError, but it gave ${error ? error.name : 'no error'}`);
    assert.equal(error.status, 500, `error.status is ${error.status}`);
  } finally {
    await server.close();
  }
});

server.js

// A small test server for this lesson, made with node:http. You do not need to change it.
import {createServer} from "node:http";

export async function startServer() {
  const counts = {}; // how often each path was requested
  const server = createServer((request, response) => {
    const path = request.url;
    counts[path] = (counts[path] ?? 0) + 1;
    const send = (status, type, text) => {
      response.writeHead(status, {"Content-Type": type});
      response.end(text);
    };
    const json = (status, value) => send(status, "application/json", JSON.stringify(value));
    if (path === "/books/1") json(200, {id: 1, title: "Dune"});
    else if (path === "/broken") send(500, "text/plain", "Server error");
    else if (path === "/flaky") {
      // busy twice (503), then it works
      if (counts[path] <= 2) send(503, "text/plain", "Busy");
      else json(200, {id: 2, title: "Emma"});
    } else if (path === "/html") send(200, "text/html", "<h1>Down for maintenance</h1>");
    else if (path === "/slow") {
      // answers after 300 ms, unless the client gave up
      const timer = setTimeout(() => json(200, {id: 3, title: "Kindred"}), 300);
      response.on("close", () => clearTimeout(timer));
    } else json(404, {error: "Not found"});
  });
  await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); // port 0: any free port
  return {
    url: `http://127.0.0.1:${server.address().port}`,
    count: (path) => counts[path] ?? 0,
    close: () => new Promise((resolve) => server.close(resolve))
  };
}

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 3

Nur vorübergehende Ausfälle wiederholen

fetchWithRetry(url, attempts) versucht es bis zu attempts-mal und wartet nach jedem Misserfolg länger. Es soll nur einen Netzwerkfehler (einen TypeError) oder einen HttpError mit einem Status ab 500 wiederholen; jeder andere Fehler muss sofort geworfen werden. Der Starter wiederholt alles, daher wird ein fehlendes Buch dreimal angefragt.

Diese Übung braucht Node.js auf Ihrem Computer (die Browser-Version kann sie nicht ausführen). Dateien und Befehle stehen unten.

Hinweise
  1. Hinweis 1

    Führen Sie die Tests aus: Der 404 wurde 3-mal angefragt. Welche Fehler können von selbst verschwinden?

  2. Hinweis 2

    Entscheiden Sie im catch-Block: ein TypeError oder ein HttpError, dessen Status 500 oder mehr ist.

  3. Hinweis 3

    const temporary = error instanceof TypeError || (error instanceof HttpError && error.status >= 500); if (!temporary || attempt === attempts) throw error;

Eine Lösung zeigen

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

export class HttpError extends Error {
  constructor(status) {
    super(`HTTP ${status}`);
    this.name = "HttpError";
    this.status = status;
  }
}

const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

export async function fetchWithRetry(url, attempts = 3) {
  for (let attempt = 1; ; attempt++) {
    try {
      const response = await fetch(url);
      if (!response.ok) throw new HttpError(response.status);
      return await response.json();
    } catch (error) {
      const temporary = error instanceof TypeError || (error instanceof HttpError && error.status >= 500);
      if (!temporary || attempt === attempts) throw error;
      await wait(10 * 2 ** attempt);
    }
  }
}
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 class HttpError extends Error {
  constructor(status) {
    super(`HTTP ${status}`);
    this.name = "HttpError";
    this.status = status;
  }
}

const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

export async function fetchWithRetry(url, attempts = 3) {
  for (let attempt = 1; ; attempt++) {
    try {
      const response = await fetch(url);
      if (!response.ok) throw new HttpError(response.status);
      return await response.json();
    } catch (error) {
      if (attempt === attempts) throw error;
      await wait(10 * 2 ** attempt);
    }
  }
}

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {startServer} from './server.js';
import {HttpError, fetchWithRetry} from './main.js';

const caught = (promise) => promise.then(() => null, (error) => error);

test('A busy server works on the third attempt', async () => {
  const server = await startServer();
  try {
    const book = await fetchWithRetry(server.url + '/flaky');
    assert.deepEqual(book, {id: 2, title: 'Emma'}, `fetchWithRetry gave ${JSON.stringify(book)}`);
    assert.equal(server.count('/flaky'), 3, `the server got ${server.count('/flaky')} requests`);
  } finally {
    await server.close();
  }
});

test('A 404 is not retried', async () => {
  const server = await startServer();
  try {
    const error = await caught(fetchWithRetry(server.url + '/books/9'));
    assert.ok(error instanceof HttpError && error.status === 404, 'fetchWithRetry should reject with an HttpError 404');
    assert.equal(server.count('/books/9'), 1, `a 404 was requested ${server.count('/books/9')} times instead of once`);
  } finally {
    await server.close();
  }
});

test('A broken server is tried three times, then the error is thrown', async () => {
  const server = await startServer();
  try {
    const error = await caught(fetchWithRetry(server.url + '/broken'));
    assert.ok(error instanceof HttpError && error.status === 500, 'fetchWithRetry should reject with an HttpError 500');
    assert.equal(server.count('/broken'), 3, `the server got ${server.count('/broken')} requests`);
  } finally {
    await server.close();
  }
});

server.js

// A small test server for this lesson, made with node:http. You do not need to change it.
import {createServer} from "node:http";

export async function startServer() {
  const counts = {}; // how often each path was requested
  const server = createServer((request, response) => {
    const path = request.url;
    counts[path] = (counts[path] ?? 0) + 1;
    const send = (status, type, text) => {
      response.writeHead(status, {"Content-Type": type});
      response.end(text);
    };
    const json = (status, value) => send(status, "application/json", JSON.stringify(value));
    if (path === "/books/1") json(200, {id: 1, title: "Dune"});
    else if (path === "/broken") send(500, "text/plain", "Server error");
    else if (path === "/flaky") {
      // busy twice (503), then it works
      if (counts[path] <= 2) send(503, "text/plain", "Busy");
      else json(200, {id: 2, title: "Emma"});
    } else if (path === "/html") send(200, "text/html", "<h1>Down for maintenance</h1>");
    else if (path === "/slow") {
      // answers after 300 ms, unless the client gave up
      const timer = setTimeout(() => json(200, {id: 3, title: "Kindred"}), 300);
      response.on("close", () => clearTimeout(timer));
    } else json(404, {error: "Not found"});
  });
  await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); // port 0: any free port
  return {
    url: `http://127.0.0.1:${server.address().port}`,
    count: (path) => counts[path] ?? 0,
    close: () => new Promise((resolve) => server.close(resolve))
  };
}

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 3 von 3

Nach einem Timeout aufgeben

getJsonWithin(url, ms) soll das geparste JSON zurückgeben, aber aufgeben, wenn innerhalb von ms Millisekunden keine Antwort kommt: Sein Promise muss dann mit einem TimeoutError abgelehnt werden. Der Starter wartet so lange, wie der Server braucht. Übergeben Sie fetch ein Timeout-Signal.

Diese Übung braucht Node.js auf Ihrem Computer (die Browser-Version kann sie nicht ausführen). Dateien und Befehle stehen unten.

Hinweise
  1. Hinweis 1

    Führen Sie die Tests aus: Die langsame Antwort kommt nach 300 ms statt eines Fehlers. Welche Option von fetch nimmt ein signal?

  2. Hinweis 2

    AbortSignal.timeout(ms) erzeugt ein Signal, das nach ms Millisekunden abbricht.

  3. Hinweis 3

    await fetch(url, {signal: AbortSignal.timeout(ms)})

Eine Lösung zeigen

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

export async function getJsonWithin(url, ms) {
  const response = await fetch(url, {signal: AbortSignal.timeout(ms)});
  return response.json();
}
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 getJsonWithin(url, ms) {
  const response = await fetch(url);
  return response.json();
}

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {startServer} from './server.js';
import {getJsonWithin} from './main.js';

test('A fast answer arrives within a second', async () => {
  const server = await startServer();
  try {
    const book = await getJsonWithin(server.url + '/books/1', 1000);
    assert.deepEqual(book, {id: 1, title: 'Dune'}, `getJsonWithin gave ${JSON.stringify(book)}`);
  } finally {
    await server.close();
  }
});

test('A slow answer rejects with a TimeoutError after 50 ms', async () => {
  const server = await startServer();
  try {
    await assert.rejects(getJsonWithin(server.url + '/slow', 50), {name: 'TimeoutError'}, 'getJsonWithin should reject with a TimeoutError');
  } finally {
    await server.close();
  }
});

server.js

// A small test server for this lesson, made with node:http. You do not need to change it.
import {createServer} from "node:http";

export async function startServer() {
  const counts = {}; // how often each path was requested
  const server = createServer((request, response) => {
    const path = request.url;
    counts[path] = (counts[path] ?? 0) + 1;
    const send = (status, type, text) => {
      response.writeHead(status, {"Content-Type": type});
      response.end(text);
    };
    const json = (status, value) => send(status, "application/json", JSON.stringify(value));
    if (path === "/books/1") json(200, {id: 1, title: "Dune"});
    else if (path === "/broken") send(500, "text/plain", "Server error");
    else if (path === "/flaky") {
      // busy twice (503), then it works
      if (counts[path] <= 2) send(503, "text/plain", "Busy");
      else json(200, {id: 2, title: "Emma"});
    } else if (path === "/html") send(200, "text/html", "<h1>Down for maintenance</h1>");
    else if (path === "/slow") {
      // answers after 300 ms, unless the client gave up
      const timer = setTimeout(() => json(200, {id: 3, title: "Kindred"}), 300);
      response.on("close", () => clearTimeout(timer));
    } else json(404, {error: "Not found"});
  });
  await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); // port 0: any free port
  return {
    url: `http://127.0.0.1:${server.address().port}`,
    count: (path) => counts[path] ?? 0,
    close: () => new Promise((resolve) => server.close(resolve))
  };
}

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

AbortError von einem Timeout erwarten

import {startServer} from "./server.js";

const server = await startServer();
try {
  await fetch(server.url + "/slow", {signal: AbortSignal.timeout(50)});
} catch (error) {
  if (error.name === "AbortError") console.log("Too slow, please try again.");
  else throw error;
} finally {
  await server.close();
}

Was Node.js ausgibt

DOMException [TimeoutError]: The operation was aborted due to timeout

Warum, und die Lösung

Ein Timeout-Signal lehnt nicht mit AbortError ab, sondern mit einer DOMException namens TimeoutError, die Prüfung verfehlt es also, und der Fehler wird weitergeworfen. Prüfen Sie für ein Timeout error.name === "TimeoutError"; AbortError heißt, dass Code abort() auf einem Controller aufgerufen hat.

Eine ok-Response, die kein JSON ist

import {startServer} from "./server.js";

const server = await startServer();
try {
  const response = await fetch(server.url + "/html");
  if (response.ok) {
    const data = await response.json();
    console.log(data);
  }
} finally {
  await server.close();
}

Was Node.js ausgibt

SyntaxError: Unexpected token '<', "<h1>Down f"... is not valid JSON

Warum, und die Lösung

Der Status war 200, aber der Body war eine HTML-Seite, wie sie ein Server in Wartung oder ein Proxy schicken kann. ok sagt nichts über das Format. Prüfen Sie response.headers.get("content-type") auf application/json, bevor Sie json() aufrufen, oder fangen Sie den SyntaxError ab und melden Sie, dass der Server etwas Unerwartetes geschickt hat.

Eine Wiederholungsschleife, die stillschweigend aufgibt

import {startServer} from "./server.js";

const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function getJsonWithRetry(url, attempts = 3) {
  for (let attempt = 1; attempt <= attempts; attempt++) {
    try {
      const response = await fetch(url);
      if (response.ok) return response.json();
    } catch {
      // a network error: try again
    }
    await wait(10 * 2 ** attempt);
  }
}

const server = await startServer();
try {
  const book = await getJsonWithRetry(server.url + "/broken");
  console.log(book.title);
} finally {
  await server.close();
}

Was Node.js ausgibt

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

Warum, und die Lösung

Nach dem letzten Versuch endet die Schleife einfach, und die Funktion gibt undefined zurück. Der Aufrufer scheitert dann weit weg von der eigentlichen Ursache, einem Serverfehler. Werfen Sie, wenn die Versuche aufgebraucht sind: Behalten Sie den letzten Fehler (einen HttpError für einen schlechten Status) und werfen Sie ihn nach der Schleife, damit der Aufrufer weiß, dass die Anfrage gescheitert ist und warum.

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

Fehlerstatus werden zu Fehlern

fetch wird für einen 404 oder 500 erfüllt, schreiben Sie also einen kleinen Helfer, der response.ok prüft und wirft, wenn es false ist. Eine Klasse HttpError extends Error mit einem Feld status, wie in Lektion I4.2, lässt Aufrufer nach Typ und Zahl entscheiden, statt Nachrichtentexte zu lesen: error instanceof HttpError && error.status === 404 heißt, dass es die Sache nicht gibt, zeigen Sie also „nicht gefunden“. Ein 500 heißt, dass der Server versagt hat: Zeigen Sie „bitte später erneut versuchen“ oder wiederholen Sie. Ohne die Prüfung wird der Fehler-Body eines 404 als Daten behandelt, oder json() scheitert auf einer Fehlerseite mit einem verwirrenden SyntaxError.

Nur wiederholen, was beim nächsten Mal klappen kann

Manche Ausfälle sind vorübergehend: ein Netzwerkfehler (TypeError: fetch failed) oder ein 5xx-Status wie 500, 502 oder 503. Dieselbe Anfrage noch einmal zu senden, kann funktionieren. Ein 4xx-Status sagt, dass die Anfrage selbst falsch ist: Ein fehlendes Buch bleibt fehlend, wiederholen Sie also nie einen 4xx. Begrenzen Sie die Versuche, zum Beispiel auf 3, und werfen Sie den letzten Fehler, wenn sie aufgebraucht sind, damit der Aufrufer erfährt, dass es gescheitert ist. Warten Sie zwischen den Versuchen, und jedes Mal länger, etwa 50, 100 und 200 ms, damit ein ausgelasteter Server Zeit hat, sich zu erholen.

Timeouts und Bodies, die kein JSON sind

fetch hat keine eigene Timeout-Option. Um nach einer selbst gewählten Zeit nicht mehr zu warten, übergeben Sie signal: AbortSignal.timeout(5000). Ist die Zeit um, wird fetch mit einer DOMException abgelehnt, deren name TimeoutError ist, und die ist ebenfalls ein Error. Eine Antwort kann ok sein und trotzdem kein JSON, etwa eine HTML-Wartungsseite: json() wird dann mit einem SyntaxError abgelehnt. Schauen Sie zuerst auf den Header content-type oder fangen Sie den SyntaxError ab und melden Sie „der Server hat etwas Unerwartetes geschickt“, statt abzustürzen.

Quellen

Zuletzt geprüft am 5. Oktober 2026