24. marts 2026

Async/await

Der er en speciel syntaks der bruges til at arbejde med promises på en mere komfortabel måde, kaldet “async/await”. Den er overraskende let at forstå og bruge.

Async functions

Lad os starte med nøgleordet async. Det kan placeres før en function, sådan her:

async function f() {
  return 1;
}

Ordet “async” før en function betyder en simpel ting: en function returnerer altid en promise. Andre værdier er automatisk pakket ind i en løst (resolved) promise.

For eksempel, denne funktion returnerer et løst promise med resultatet 1; lad os teste den:

async function f() {
  return 1;
}

f().then(alert); // 1

… vi kunne også eksplicit returnere et promise, hvilket ville være det samme:

async function f() {
  return Promise.resolve(1);
}

f().then(alert); // 1

async sikrer at funktionen returnerer et promise, og pakker ikke-promise værdier ind i et løst promise. Enkelt nok, ikke? Men ikke kun det. Der er endnu et nøgleord, await, som kun virker inde i async funktioner, og det er ret sejt.

Await

Syntaksen er:

// virker kun inde i async funktioner
let value = await promise;

Nøgleordet await gør at JavaScript venter indtil det promise er løst og returnerer dets resultat.

Her er et eksempel med et promise, der løses om 1 sekund:

async function f() {

  let promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("done!"), 1000)
  });

  let result = await promise; // vent indtil promise løses (*)

  alert(result); // "færdig!"
}

f();

Udførelsen af funktionen “sættes på pause” ved linjen (*) og fortsætter når promise løses, med result som dens resultat. Koden ovenfor vil vise “færdig!” efter 1 sekund.

Lad os understrege: await udsætter eksekvering af funktionen indtil promise løses og fortsætter med det modtagne resultat. Det koster ikke nogen CPU ressourcer, fordi JavaScript motoren kan udføre andre job i mellemtiden: udføre andre scripts, håndtere events etc.

Det er bare en mere elegant syntaks for at få resultatet af et promise end promise.then. Og det er lettere at læse og skrive.

Du kan ikke bruge await i regulære funktioner

HVis vi prøver at bruge await i en ikke-async funktion, vil der være en syntaxfejl:

function f() {
  let promise = Promise.resolve(1);
  let result = await promise; // Syntax error
}

Vi får den fejl hvis vi glemmer at tilføje async før en funktion. Som nævnt tidligere, virker await kun inde i en async funktion.

Lad os tage showAvatar() eksemplet fra kapitlet Sammenkædning af promises (chaining) og omskrive det ved hjælp af async/await:

  1. Vi vil skulle erstatte .then kald med await.
  2. Desuden bør vi gøre funktionen async for at de kan fungere.
async function showAvatar() {

  // læs JSON filen
  let response = await fetch('/article/promise-chaining/user.json');
  let user = await response.json();

  // læs github bruger
  let githubResponse = await fetch(`https://api.github.com/users/${user.name}`);
  let githubUser = await githubResponse.json();

  // vis avatar
  let img = document.createElement('img');
  img.src = githubUser.avatar_url;
  img.className = "promise-avatar-example";
  document.body.append(img);

  // vent 3 sekunder
  await new Promise((resolve, reject) => setTimeout(resolve, 3000));

  img.remove();

  return githubUser;
}

showAvatar();

Ret rent og nemt at læse, ikke? Meget bedre end før.

Moderne browsere tillader top-level await i moduler

I moderne browsere vil await på top level fungere helt fint, når vi er inde i et modul. Vi vil dække moduler i artiklen Moduler, introduktion.

For eksempel:

// vi tager udgangspunkt i, at denne kode kører på top level, inde i et modul
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();

console.log(user);

Hvis vi ikke bruger moduler, eller ældre browsere skal understøttes, er der en universel opskrift: pak det hele ind i en anonym async funktion.

Sådan her:

(async () => {
  let response = await fetch('/article/promise-chaining/user.json');
  let user = await response.json();
  ...
})();
await accepterer “thenables”

Ligesom promise.then vil await tillade os at bruge thenable objekter (dem med en meode then der kan kaldes). Idéen med dette er at 3de-parts objekter ikke nødvendigvis er et promise, men promise-kompatible: hvis det understøtter .then, er det godt nok til at bruge sammen med await.

Her er en demo Thenable klasse; await nedenfor accepterer udgaver af den:

class Thenable {
  constructor(num) {
    this.num = num;
  }
  then(resolve, reject) {
    alert(resolve);
    // resolve med this.num*2 efter 1000ms
    setTimeout(() => resolve(this.num * 2), 1000); // (*)
  }
}

async function f() {
  // venter i 1 sekund, hvorefter resultatet bliver 2
  let result = await new Thenable(1);
  alert(result);
}

f();

Hvis await får et ikke-promise object med .then, det kalder den metode og giver de indbyggede funktioner resolve og reject som argumenter (ligesom det gør for en almindelig Promise executor). Derefter venter await indtil en af dem bliver kaldt (i eksemplet ovenfor sker det i linjen (*)) og fortsætter derefter med resultatet.

Async klasse metoder

For at deklarere en async klasse metode, tilføj async før metoden:

class Waiter {
  async wait() {
    return await Promise.resolve(1);
  }
}

new Waiter()
  .wait()
  .then(alert); // 1 (det er det samme som (result => alert(result)))

Meningen er den samme: det sikrer at den returnerede værdi er et promise og aktiverer await.

Håndtering af fejl

Hvis et promise løses normalt, så returnerer await promise resultatet. Men i tilfældet af en afvisning, kaster det en fejl, som om der var en throw-sætning på linjen.

Denne kode:

async function f() {
  await Promise.reject(new Error("Ups!"));
}

… er det samme som:

async function f() {
  throw new Error("Ups!");
}

I virkelige situationer vil et promise tage noget tid før det afvises. I det tilfælde vil der være en forsinkelse før await kaster en fejl.

Vi kan opsnappe den fejl ved hjælp af try..catch, på samme måde som en almindelig throw:

async function f() {

  try {
    let response = await fetch('http://no-such-url');
  } catch(err) {
    alert(err); // TypeError: failed to fetch
  }
}

f();

I tilfælde af en fejl, springer kontrollen over til catch-blokken. Vi kan også wrappe flere linjer i try..catch for at fange fejl i flere await:

async function f() {

  try {
    let response = await fetch('/no-user-here');
    let user = await response.json();
  } catch(err) {
    // fanger fejler i både fetch og response.json
    alert(err);
  }
}

f();

Hvis vi ikke har try..catch, så vil promise genreret ved kaldet af den asynkrone funktion f() blive afvist. Vi kan tilføje .catch for at håndtere det:

async function f() {
  let response = await fetch('http://no-such-url');
}

// f() bliver et afvist promise
f().catch(alert); // TypeError: fejlede ved fetch // (*)

Hvis vi glemmer at tilføje .catch, så får vi en ikke-håndteret promise-fejl (synlig i konsollen). Vi kan fange sådanne fejl ved hjælp af en global unhandledrejection-begivenhedshåndtering som beskrevet i kapitlet Håndtering af fejl med promises.

async/await og promise.then/catch

Når vi bruger async/await, bruger vi sjældent .then, fordi await håndterer ventningen for os. Og vi kan bruge en almindelig try..catch i stedet for .catch. Det er ofte (men ikke altid) mere praktisk.

Men på den øverste niveau af koden, når vi er uden for enhver async funktion, er vi syntaktisk ude af stand til at bruge await, så det er en normal praksis at tilføje .then/catch for at håndtere det endelige resultat eller fejlen, som i linjen (*) i eksemplet ovenfor.

async/await virker godt med Promise.all

Når vi har brug for at vente på flere promises, kan vi pakke dem ind i Promise.all og derefter await:

// vent på arrayet af resultater
let results = await Promise.all([
  fetch(url1),
  fetch(url2),
  ...
]);

I tilfælde af en fejl, propagerer den som sædvanligt, fra det mislykkede promise til Promise.all, og så bliver det til en exception, som vi kan fange ved hjælp af try..catch omkring kaldet.

Opsummering

Nøgleordet async før en funktion har to effekter:

  1. Gør det til en funktion, der altid returnerer et promise.
  2. Tillader await at blive brugt i den.

Nøgleordet await før et promise gør JavaScript til at vente indtil det promise løses, og derefter:

  1. Hvis det er en fejl, genereres en exception — samme som hvis throw error blev kaldt på det sted.
  2. Ellers returnerer det resultatet.

Sammen udgør de en fremragende framework til at skrive asynkron kode, som er let at læse og skrive.

Med async/await behøver vi sjældent at skrive promise.then/catch, men vi bør stadig ikke glemme, at de er baseret på promises, fordi nogle gange (f.eks. i den yderste scope) er vi nødt til at bruge dem. Derudover er Promise.all ret smart når vi venter på mange opgaver samtidigt.

Opgaver

Omskriv dette eksempelkode fra kapitlet Sammenkædning af promises (chaining) ved hjælp af async/await i stedet for .then/catch:

function loadJson(url) {
  return fetch(url)
    .then(response => {
      if (response.status == 200) {
        return response.json();
      } else {
        throw new Error(response.status);
      }
    });
}

loadJson('https://javascript.info/no-such-user.json')
  .catch(alert); // Error: 404

Noterne er under koden:

async function loadJson(url) { // (1)
  let response = await fetch(url); // (2)

  if (response.status == 200) {
    let json = await response.json(); // (3)
    return json;
  }

  throw new Error(response.status);
}

loadJson('https://javascript.info/no-such-user.json')
  .catch(alert); // Error: 404 (4)

Noter:

  1. Funktionen loadJson bliver async.

  2. Alle .then indeni erstattes med await.

  3. Vi kan bruge return response.json() i stedet for at vente på det, som dette:

    if (response.status == 200) {
      return response.json(); // (3)
    }

    Den ydre kode vil være nødt til at vente med await på at det promise løses. I vores tilfælde spiller det ikke en rolle.

  4. Fejlen kastet fra loadJson håndteres af .catch. Vi kan ikke bruge await loadJson(…) der, fordi vi ikke er i en async funktion.

Nedenfor ser du “rethrow” eksemplet. Omskriv det ved hjælp af async/await i stedet for .then/catch.

Og erstat rekursionen med et loop i demoGithubUser: med async/await bliver det nemt at gøre.

class HttpError extends Error {
  constructor(response) {
    super(`${response.status} for ${response.url}`);
    this.name = 'HttpError';
    this.response = response;
  }
}

function loadJson(url) {
  return fetch(url)
    .then(response => {
      if (response.status == 200) {
        return response.json();
      } else {
        throw new HttpError(response);
      }
    });
}

// Spørg efter et brugernavn indtil github returnerer en gyldig bruger
function demoGithubUser() {
  let name = prompt("Skriv et brugernavn?", "iliakan");

  return loadJson(`https://api.github.com/users/${name}`)
    .then(user => {
      alert(`Fulde navn: ${user.name}.`);
      return user;
    })
    .catch(err => {
      if (err instanceof HttpError && err.response.status == 404) {
        alert("Brugeren findes ikke. Indtast et nyt brugernavn.");
        return demoGithubUser();
      } else {
        throw err;
      }
    });
}

demoGithubUser();

Der er ikke nogen tricks her. Bare erstat .catch med try..catch inde i demoGithubUser og tilføj async/await hvor det er nødvendigt:

class HttpError extends Error {
  constructor(response) {
    super(`${response.status} for ${response.url}`);
    this.name = 'HttpError';
    this.response = response;
  }
}

async function loadJson(url) {
  let response = await fetch(url);
  if (response.status == 200) {
    return response.json();
  } else {
    throw new HttpError(response);
  }
}

// Spørg efter et brugernavn indtil github returnerer en gyldig bruger
async function demoGithubUser() {

  let user;
  while(true) {
    let name = prompt("Skriv et brugernavn?", "iliakan");

    try {
      user = await loadJson(`https://api.github.com/users/${name}`);
      break; // ingen fejl, hop ud af loopet
    } catch(err) {
      if (err instanceof HttpError && err.response.status == 404) {
        // loop fortsætter efter alert
        alert("Brugeren findes ikke. Indtast et nyt brugernavn.");
      } else {
        // ukendt fejl, rethrow
        throw err;
      }
    }
  }


  alert(`Fulde navn: ${user.name}.`);
  return user;
}

demoGithubUser();

Vi har en “normal” function kaldet f. Hvordan kan du kalde den async function wait() og bruge dens resultat inde i f?

async function wait() {
  await new Promise(resolve => setTimeout(resolve, 1000));

  return 10;
}

function f() {
  // ... hvad skal vi skrive her?
  // vi har brug for at kalde den asynkrone wait() og vente på at få 10
  // husk, vi kan ikke bruge "await"
}

P.S. Opgaven er teknisk set meget simpel, men spørgsmålet er ganske almindeligt for udviklere, der er nye i async/await.

Det er her hvor det er godt at vide hvordan det virker inde i motorrummet.

Du kan bare behandle async kald som et promise og tilføje .then til det:

async function wait() {
  await new Promise(resolve => setTimeout(resolve, 1000));

  return 10;
}

function f() {
  // viser 10 efter 1 sekund
  wait().then(result => alert(result));
}

f();

Promise.all er en fantastisk måde at køre flere operationer parallelt. Det er især nyttigt når vi har brug for at lave flere forespørgsler til forskellige services samtidigt.

Men, der er en skjult fare. Vi vil se et eksempel i denne opgave og udforske, hvordan man undgår den.

Lad os sige, vi har en forbindelse til en ekstern service, såsom en database.

Der er to funktioner: connect() og disconnect().

Når den er forbundet, kan vi sende forespørgsler ved hjælp af database.query(...) – en async function, som normalt returnerer resultatet, men også kan kaste en fejl.

Her er en simpel implementering af det:

let database;

function connect() {
  database = {
    async query(isOk) {
      if (!isOk) throw new Error('Query failed');
    }
  };
}

function disconnect() {
  database = null;
}

// beregnet brug:
// connect()
// ...
// database.query(true) for at emulere et succesfuldt kald
// database.query(false) for at emulere et mislykket kald
// ...
// disconnect()

Se her er problemet.

Vi skrev koden til at forbinde og sende 3 forespørgsler parallelt (alle tager forskellig tid, f.eks. 100, 200 og 300 ms), for derefter at frakoble igen:

// Hjælperfunktion til at kalde async funktion `fn` efter `ms` millisekunder
function delay(fn, ms) {
  return new Promise((resolve, reject) => {
    setTimeout(() => fn().then(resolve, reject), ms);
  });
}

async function run() {
  connect();

  try {
    await Promise.all([
      // disse 3 paralelle jobs tager forskellig tid: 100, 200 og 300 ms
      // vi bruger `delay` hjælperen for at opnå denne effekt
      delay(() => database.query(true), 100),
      delay(() => database.query(false), 200),
      delay(() => database.query(false), 300)
    ]);
  } catch(error) {
    console.log('Fejl håndteret (eller er den?)');
  }

  disconnect();
}

run();

To af disse forespørgsler viser sig at fejle, men vi var smarte nok til at wrappe Promise.all kaldet i en try..catch block.

Men lige meget hvad, så hjælper det ikke! Dette script fører faktisk til en ikke-fanget fejl i konsollen!

Hvorfor? Hvordan undgår man det?

Roden til problemet er, at Promise.all umiddelbart afviser, når en af dens promises afviser, men den gør intet for at annullere de andre promises.

I vores tilfælde fejler den anden forespørgsel, så Promise.all afviser, og try...catch blokken fanger denne fejl. Mens de andre promises ikke er påvirket – de fortsætter uafhængigt deres eksekvering. I vores tilfælde kaster den tredje forespørgsel en fejl selv efter et stykke tid. Og den fejl bliver aldrig fanget, vi kan se den i konsollen.

Problemet er især farligt i server-side miljøer, såsom Node.js, hvor en ikke-fanget fejl kan forårsage, at processen går ned.

Hvordan fikser vi det?

En idéel løsning ville være at annullere alle uafsluttede forespørgsler, når en af dem fejler. På denne måde undgår vi eventuelle fejl.

Men den dårlige nyheder er, at servicekald (såsom database.query) ofte er implementeret af en 3rd-parts bibliotek, som ikke understøtter annullering. Så der er ingen måde at annullere et kald.

Som et alternativ kan vi skrive vores egen wrapper omkring Promise.all som tilføjer en custom then/catch handler til hver promise for at spore dem: resultaterne samles og, hvis en fejl opstår, ignoreres alle efterfølgende promises.

function customPromiseAll(promises) {
  return new Promise((resolve, reject) => {
    const results = [];
    let resultsCount = 0;
    let hasError = false; // vi sætter den til true ved første fejl vi møder

    promises.forEach((promise, index) => {
      promise
        .then(result => {
          if (hasError) return; // ignorer promise hvis den allerede er fejlet
          results[index] = result;
          resultsCount++;
          if (resultsCount === promises.length) {
            resolve(results); // når alle resultater er klar - succes
          }
        })
        .catch(error => {
          if (hasError) return; // ignorer promise hvis den allerede er fejlet
          hasError = true; // ups, fejl!
          reject(error); // fejl med reject
        });
    });
  });
}

Denne tilgang har sine egne udfordringer – det er ofte uønsket at kalde disconnect() når der stadig er forespørgsler i processen.

Det kan være vigtigt at alle forespørgsler gennemføres, især hvis nogle af dem indeholder vigtige opdateringer.

Så vi bør vente indtil alle promise er afsluttet, før vi går videre med eksekveringen og til sidst frakobler.

Her er en anden implementering. Den opfører sig i stil med Promise.all – den resolver også ved den første fejl, men venter indtil alle promise er afsluttet.

function customPromiseAllWait(promises) {
  return new Promise((resolve, reject) => {
    const results = new Array(promises.length);
    let settledCount = 0;
    let firstError = null;

    promises.forEach((promise, index) => {
      Promise.resolve(promise)
        .then(result => {
          results[index] = result;
        })
        .catch(error => {
          if (firstError === null) {
            firstError = error;
          }
        })
        .finally(() => {
          settledCount++;
          if (settledCount === promises.length) {
            if (firstError !== null) {
              reject(firstError);
            } else {
              resolve(results);
            }
          }
        });
    });
  });
}

Nu vil await customPromiseAllWait(...) tilbageholde udførelsen indtil alle forespørgsler er behandlet. Hvis der opstår en fejl, vil den blive fanget i try...catch blokken, og vi kan være sikre på, at alle forespørgsler er afsluttet, før vi går videre.

Dette er en mere pålidelig tilgang, da den garanterer et forudsigeligt eksekveringsflow.

Til sidst, hvis vi vil behandle alle fejl, kan vi bruge enten Promise.allSettled eller skrive en wrapper omkring for at samle alle fejl i et enkelt AggregateError objekt og afvise med det.

// vent på at alle promise er afsluttet
// returner resultater hvis ingen fejl
// kast AggregateError med alle fejl hvis nogen
function allOrAggregateError(promises) {
  return Promise.allSettled(promises).then(results => {
    const errors = [];
    const values = [];

    results.forEach((res, i) => {
      if (res.status === 'fulfilled') {
        values[i] = res.value;
      } else {
        errors.push(res.reason);
      }
    });

    if (errors.length > 0) {
      throw new AggregateError(errors, 'En eller flere promises fejlede');
    }

    return values;
  });
}
Tutorial-oversigt

Kommentarer

læs dette før du kommenterer…
  • Hvis du har forslag til forbedringer - så opret venligst et GitHub-issue eller en pull request i stedet for at kommentere.
  • Hvis du ikke forstår noget i artiklen - så uddyb venligst.
  • For at indsætte få ord kode, brug <code>-taggen, for flere linjer - omslut dem i <pre>-tag, for mere end 10 linjer - brug en sandbox (plnkr, jsbin, codepen…)