Start · Sprachen · JavaScript · Referenz · async function*

async function*

Anweisung

Die <code>async function</code>-Deklaration erzeugt ein Binding einer neuen asynchronen Funktion an einen Namen; das Schlüsselwort <code>await</code> ist im Funktionskörper erlaubt.

seit JavaScript ES2018 Kategorie: syntax

Signatur

async function name(param0, param1, /* …, */ paramN) { statements }

Beschreibung

Die async function-Deklaration erzeugt ein Binding einer neuen async function an einen gegebenen Namen. Das Schlüsselwort await ist innerhalb des Funktionskörpers erlaubt und ermöglicht es, asynchrones, Promise-basiertes Verhalten in einem saubereren Stil zu schreiben, ohne dass Promise-Chains explizit konfiguriert werden müssen.

Async functions können auch mit dem async function-Ausdruck definiert werden.

Hinweis: Zwischen async und function darf kein Zeilenumbruch stehen, da sonst automatisch ein Semikolon eingefügt wird, wodurch async zu einem Bezeichner und der Rest zu einer function-Deklaration wird.

Eine async function-Deklaration erzeugt ein AsyncFunction-Objekt. Jedes Mal, wenn eine async function aufgerufen wird, gibt sie ein neues Promise zurück, das entweder mit dem von der async function zurückgegebenen Wert erfüllt (resolved) oder mit einer innerhalb der async function nicht abgefangenen Exception zurückgewiesen (rejected) wird.

Async functions können null oder mehr await-Ausdrücke enthalten. Await-Ausdrücke lassen Promise-zurückgebende Funktionen so wirken, als wären sie synchron, indem sie die Ausführung anhalten, bis das zurückgegebene Promise erfüllt oder zurückgewiesen wurde. Der aufgelöste Wert des Promise wird als Rückgabewert des await-Ausdrucks behandelt. Die Verwendung von async und await ermöglicht den Einsatz normaler try/catch-Blöcke um asynchronen Code.

Hinweis: Das Schlüsselwort await ist in regulärem JavaScript-Code nur innerhalb von async functions gültig. Wird es außerhalb eines async-function-Körpers verwendet, entsteht ein SyntaxError. In JavaScript-Modulen kann await auch eigenständig verwendet werden.

Hinweis: Der Zweck von async/await ist, die Syntax für den Umgang mit Promise-basierten APIs zu vereinfachen. Das Verhalten von async/await ähnelt einer Kombination aus generators und promises.

Async functions geben stets ein Promise zurück. Ist der Rückgabewert einer async function nicht explizit ein Promise, wird er implizit in ein Promise verpackt.

Beachten Sie: Auch wenn sich der Rückgabewert einer async function so verhält, als wäre er in ein Promise.resolve verpackt, sind die beiden nicht äquivalent. Eine async function liefert eine andere Referenz, während Promise.resolve dieselbe Referenz zurückgibt, wenn der übergebene Wert bereits ein Promise ist. Das kann problematisch sein, wenn man die Gleichheit eines Promise und des Rückgabewerts einer async function prüfen möchte.

Der Körper einer async function kann als durch null oder mehr await-Ausdrücke unterteilt betrachtet werden. Der Top-Level-Code bis einschließlich des ersten await-Ausdrucks (falls vorhanden) wird synchron ausgeführt. Somit läuft eine async function ohne await-Ausdruck synchron. Enthält der Funktionskörper jedoch einen await-Ausdruck, wird die async function immer asynchron abgeschlossen.

Code nach jedem await-Ausdruck kann so gedacht werden, als existiere er in einem .then-callback. Auf diese Weise wird eine Promise-Chain schrittweise mit jedem Wiedereintritt in die Funktion aufgebaut. Der Rückgabewert bildet das letzte Glied der Kette.

Im folgenden Beispiel werden nacheinander zwei Promises awaited. Der Fortschritt durch die Funktion foo verläuft in drei Phasen.

  1. Die erste Zeile des Körpers von foo wird synchron ausgeführt, wobei der await-Ausdruck mit dem pending Promise konfiguriert wird. Anschließend wird der Fortschritt in foo ausgesetzt und die Kontrolle an die aufrufende Funktion zurückgegeben.
  2. Einige Zeit später, wenn das erste Promise entweder erfüllt oder zurückgewiesen wurde, kehrt die Kontrolle zu foo zurück. Das Ergebnis der Erfüllung des ersten Promise (falls nicht zurückgewiesen) wird vom await-Ausdruck zurückgegeben. Hier wird 1 an result1 zugewiesen. Die Ausführung geht weiter, und der zweite await-Ausdruck wird ausgewertet. Wiederum wird der Fortschritt in foo ausgesetzt und die Kontrolle abgegeben.
  3. Einige Zeit später, wenn das zweite Promise entweder erfüllt oder zurückgewiesen wurde, kehrt die Kontrolle zurück in foo. Das Ergebnis der Auflösung des zweiten Promise wird vom zweiten await-Ausdruck zurückgegeben. Hier wird 2 an result2 zugewiesen. Die Ausführung wird zum return-Ausdruck (falls vorhanden) fortgesetzt. Der Standard-Rückgabewert undefined wird als Auflösungswert des aktuellen Promise zurückgegeben.

Beachten Sie, dass die Promise-Chain nicht in einem Zug aufgebaut wird. Stattdessen wird sie schrittweise aufgebaut, während die Kontrolle sukzessive von und zur async function übergeben wird. Deshalb müssen wir beim Umgang mit gleichzeitig laufenden asynchronen Operationen auf das Fehlerbehandlungsverhalten achten.

Beispielsweise wird im folgenden Code ein unbehandelter Promise-Rejection-Fehler geworfen, selbst wenn weiter unten in der Promise-Chain ein .catch-Handler konfiguriert wurde. Das liegt daran, dass p2 erst dann in die Promise-Chain „eingehängt" wird, wenn die Kontrolle von p1 zurückkehrt.

async function-Deklarationen verhalten sich ähnlich wie function-Deklarationen — sie werden an den Anfang ihres Scopes gehoisted und können überall in ihrem Scope aufgerufen werden, und sie können nur in bestimmten Kontexten neu deklariert werden.

Parameter

Name Typ Default Beschreibung
$name Pflicht string — Der Name der Funktion.
$param any — Der Name eines formalen Parameters der Funktion. Zur Parameter-Syntax siehe die Functions-Referenz.
$statements any — Die Anweisungen, die den Körper der Funktion bilden. Der await-Mechanismus kann verwendet werden.

Rückgabewert

Typ
Promise
Beschreibung
Ein Promise, das mit dem von der async function zurückgegebenen Wert erfüllt oder mit einer nicht abgefangenen Exception zurückgewiesen wird.

Beispiele

Einführungsbeispiel

function resolveAfter2Seconds() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("resolved");
    }, 2000);
  });
}

async function asyncCall() {
  console.log("calling");
  const result = await resolveAfter2Seconds();
  console.log(result);
  // Expected output: "resolved"
}

asyncCall();
calling resolved

Implizites Promise-Wrapping

async function foo() {
  return 1;
}

Ähnlich zu Promise.resolve

function foo() {
  return Promise.resolve(1);
}

Referenzgleichheit von async Rückgabewerten

const p = new Promise((res, rej) => {
  res(1);
});

async function asyncReturn() {
  return p;
}

function basicReturn() {
  return Promise.resolve(p);
}

console.log(p === basicReturn()); // true
console.log(p === asyncReturn()); // false

await eines Werts

async function foo() {
  await 1;
}

Äquivalent zu await 1

function foo() {
  return Promise.resolve(1).then(() => undefined);
}

Aufeinanderfolgende awaits

async function foo() {
  const result1 = await new Promise((resolve) =>
    setTimeout(() => resolve("1")),
  );
  const result2 = await new Promise((resolve) =>
    setTimeout(() => resolve("2")),
  );
}
foo();

Unbehandelter Rejection-Fehler bei nacheinander awaited Promises

async function foo() {
  const p1 = new Promise((resolve) => setTimeout(() => resolve("1"), 1000));
  const p2 = new Promise((_, reject) =>
    setTimeout(() => reject(new Error("failed")), 500),
  );
  const results = [await p1, await p2]; // Do not do this! Use Promise.all or Promise.allSettled instead.
}
foo().catch(() => {}); // Attempt to swallow all errors...

Async functions und Ausführungsreihenfolge

function resolveAfter2Seconds() {
  console.log("starting slow promise");
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("slow");
      console.log("slow promise is done");
    }, 2000);
  });
}

function resolveAfter1Second() {
  console.log("starting fast promise");
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("fast");
      console.log("fast promise is done");
    }, 1000);
  });
}

async function sequentialStart() {
  console.log("== sequentialStart starts ==");

  // 1. Timer starten, nach Abschluss loggen
  const slow = resolveAfter2Seconds();
  console.log(await slow);

  // 2. Nächsten Timer erst nach Warten auf den vorherigen starten
  const fast = resolveAfter1Second();
  console.log(await fast);

  console.log("== sequentialStart done ==");
}

async function sequentialWait() {
  console.log("== sequentialWait starts ==");

  // 1. Zwei Timer starten, ohne aufeinander zu warten
  const slow = resolveAfter2Seconds();
  const fast = resolveAfter1Second();

  // 2. Auf den langsamen Timer warten und dann das Ergebnis loggen
  console.log(await slow);
  // 3. Auf den schnellen Timer warten und dann das Ergebnis loggen
  console.log(await fast);

  console.log("== sequentialWait done ==");
}

async function concurrent1() {
  console.log("== concurrent1 starts ==");

  // 1. Zwei Timer gleichzeitig starten und auf beide warten
  const results = await Promise.all([
    resolveAfter2Seconds(),
    resolveAfter1Second(),
  ]);
  // 2. Die Ergebnisse zusammen loggen
  console.log(results[0]);
  console.log(results[1]);

  console.log("== concurrent1 done ==");
}

async function concurrent2() {
  console.log("== concurrent2 starts ==");

  // 1. Zwei Timer gleichzeitig starten, jeweils unmittelbar nach Abschluss loggen
  await Promise.all([
    (async () => console.log(await resolveAfter2Seconds()))(),
    (async () => console.log(await resolveAfter1Second()))(),
  ]);
  console.log("== concurrent2 done ==");
}

sequentialStart(); // nach 2 Sekunden loggt "slow", dann nach 1 weiteren Sekunde "fast"

// oben abwarten
setTimeout(sequentialWait, 4000); // nach 2 Sekunden loggt "slow" und dann "fast"

// erneut warten
setTimeout(concurrent1, 7000); // gleich wie sequentialWait

// erneut warten
setTimeout(concurrent2, 10000); // nach 1 Sekunde loggt "fast", dann nach 1 weiteren Sekunde "slow"

Promise-Chain (vorher)

function getProcessedData(url) {
  return downloadData(url) // returns a promise
    .catch((e) => downloadFallbackData(url)) // returns a promise
    .then((v) => processDataInWorker(v)); // returns a promise
}

Als async function umgeschrieben

async function getProcessedData(url) {
  let v;
  try {
    v = await downloadData(url);
  } catch (e) {
    v = await downloadFallbackData(url);
  }
  return processDataInWorker(v);
}

Alternative mit catch()

async function getProcessedData(url) {
  const v = await downloadData(url).catch((e) => downloadFallbackData(url));
  return processDataInWorker(v);
}

// Wichtig · Fallstricke

Warnung: Die Funktionen sequentialWait und concurrent1 sind nicht funktional äquivalent. In sequentialWait entsteht, wenn Promise fast zurückgewiesen wird, bevor Promise slow erfüllt ist, ein unbehandelter Promise-Rejection-Fehler, unabhängig davon, ob der Aufrufer eine catch-Klausel konfiguriert hat. In concurrent1 verdrahtet Promise.all die Promise-Chain in einem Zug, sodass die Operation unabhängig von der Reihenfolge der Rejections fail-fast fehlschlägt und der Fehler stets innerhalb der konfigurierten Promise-Chain auftritt und normal abgefangen werden kann.