Begrip van de gebeurtenisloop, terugbellen, beloften en async/await in JavaScript

De auteur heeft het COVID-19 Relief Fund geselecteerd om een donatie te ontvangen als onderdeel van het Write for DOnations-programma.

Introductie

In de begindagen van het internet bestonden websites vaak uit statische gegevens op een HTML-pagina. Maar nu webapplicaties interactiever en dynamischer zijn geworden, is het steeds noodzakelijker geworden om intensieve bewerkingen uit te voeren, zoals het maken van externe netwerkverzoeken om API-gegevens op te halen. Om deze bewerkingen in JavaScript te verwerken, moet een ontwikkelaar asynchrone programmeertechnieken gebruiken.

Aangezien JavaScript een single-threaded programmeertaal is met een synchroon uitvoeringsmodel dat de ene bewerking na de andere verwerkt, kan het slechts één verklaring tegelijk verwerken. Een handeling zoals het aanvragen van gegevens van een API kan echter een onbepaalde tijd in beslag nemen, afhankelijk van de grootte van de aangevraagde gegevens, de snelheid van de netwerkverbinding en andere factoren. Als API-aanroepen op een synchrone manier zouden worden uitgevoerd, zou de browser geen gebruikersinvoer kunnen verwerken, zoals scrollen of op een knop klikken, totdat die bewerking is voltooid. Dit staat bekend als blokkeren.

Om blokkerend gedrag te voorkomen, heeft de browseromgeving veel Web-API’s waar JavaScript toegang toe kan hebben die asynchroon zijn, wat betekent dat ze parallel kunnen worden uitgevoerd met andere bewerkingen in plaats van sequentieel. Dit is handig omdat het de gebruiker in staat stelt de browser normaal te blijven gebruiken terwijl de asynchrone bewerkingen worden verwerkt.

Als JavaScript-ontwikkelaar moet je weten hoe je kunt werken met asynchrone Web-API’s en hoe je de respons of fout van die bewerkingen kunt afhandelen. In dit artikel leer je over de gebeurtenislus, de oorspronkelijke manier om met asynchroon gedrag om te gaan via terugroepfuncties, de bijgewerkte ECMAScript 2015-toevoeging van beloftes, en de moderne praktijk van het gebruik van async/await.

Opmerking: Dit artikel richt zich op client-side JavaScript in de browseromgeving. Dezelfde concepten zijn over het algemeen waar in de Node.js-omgeving, echter Node.js gebruikt zijn eigen C++ API’s in plaats van de Web-API’s van de browser. Voor meer informatie over asynchroon programmeren in Node.js, bekijk Hoe Asynchrone Code te Schrijven in Node.js.

De Gebeurtenislus

Deze sectie zal uitleggen hoe JavaScript asynchrone code behandelt met de event loop. Het zal eerst een demonstratie van de event loop in actie doorlopen en zal vervolgens de twee elementen van de event loop uitleggen: de stack en de queue.

JavaScript code die geen gebruik maakt van enige asynchrone Web API’s zal synchroon uitgevoerd worden – één voor één, sequentieel. Dit wordt gedemonstreerd door deze voorbeeldcode die drie functies oproept die elk een nummer naar de console afdrukken:

// Definieer drie voorbeeldfuncties
function first() {
  console.log(1)
}

function second() {
  console.log(2)
}

function third() {
  console.log(3)
}

In deze code definieer je drie functies die getallen afdrukken met console.log().

Vervolgens roep je de functies aan:

// Voer de functies uit
first()
second()
third()

De output zal gebaseerd zijn op de volgorde waarin de functies werden aangeroepen – first(), second(), dan third():

Output
1 2 3

Wanneer een asynchrone Web API wordt gebruikt, worden de regels ingewikkelder. Een ingebouwde API waarmee je dit kunt testen is setTimeout, die een timer instelt en een actie uitvoert na een bepaalde tijd. setTimeout moet asynchroon zijn, anders zou de hele browser bevroren blijven tijdens het wachten, wat zou resulteren in een slechte gebruikerservaring.

Voeg setTimeout toe aan de second functie om een asynchrone aanvraag te simuleren:

// Definieer drie voorbeeldfuncties, maar één ervan bevat asynchrone code
function first() {
  console.log(1)
}

function second() {
  setTimeout(() => {
    console.log(2)
  }, 0)
}

function third() {
  console.log(3)
}

setTimeout neemt twee argumenten: de functie die het asynchroon zal uitvoeren en de hoeveelheid tijd die het zal wachten voordat het die functie aanroept. In deze code heb je console.log ingepakt in een anonieme functie en die doorgegeven aan setTimeout, vervolgens heb je de functie ingesteld om uit te voeren na 0 milliseconden.

Roep nu de functies aan, zoals je eerder hebt gedaan:

// Voer de functies uit
first()
second()
third()

Je zou verwachten dat met een setTimeout ingesteld op 0 het uitvoeren van deze drie functies nog steeds resulteert in de getallen die sequentieel worden afgedrukt. Maar omdat het asynchroon is, zal de functie met de time-out als laatste worden afgedrukt:

Output
1 3 2

Het maakt niet uit of je de time-out op nul seconden of vijf minuten instelt – de console.log die wordt aangeroepen door asynchrone code zal worden uitgevoerd na de synchrone top-level functies. Dit gebeurt omdat de JavaScript-hostomgeving, in dit geval de browser, een concept genaamd de gebeurtenislus gebruikt om gelijktijdige of parallelle gebeurtenissen af te handelen. Omdat JavaScript slechts één verklaring tegelijk kan uitvoeren, moet de gebeurtenislus worden geïnformeerd wanneer welke specifieke verklaring moet worden uitgevoerd. De gebeurtenislus behandelt dit met de concepten van een stack en een wachtrij.

Stack

De stack, of call stack, houdt de staat bij van welke functie momenteel wordt uitgevoerd. Als je niet bekend bent met het concept van een stack, kun je het je voorstellen als een array met “Last in, first out” (LIFO) eigenschappen, wat betekent dat je alleen items kunt toevoegen of verwijderen van het einde van de stack. JavaScript voert het huidige frame (of functieoproep in een specifieke omgeving) uit in de stack, verwijdert het vervolgens en gaat door naar de volgende.

Voor het voorbeeld met alleen synchrone code, behandelt de browser de uitvoering in de volgende volgorde:

  • Voeg first() toe aan de stack, voer first() uit, dat 1 naar de console logt, verwijder first() uit de stack.
  • Voeg second() toe aan de stack, voer second() uit, dat 2 naar de console logt, verwijder second() uit de stack.
  • Voeg third() toe aan de stack, voer third() uit, dat 3 naar de console logt, verwijder third() uit de stack.

Het tweede voorbeeld met setTimeout ziet er als volgt uit:

  • Voeg first() toe aan de stack, voer first() uit, dat 1 naar de console logt, verwijder first() uit de stack.
  • Voeg second() toe aan de stack, voer second() uit.
    • Voeg setTimeout() toe aan de stack, voer de setTimeout() Web API uit, die een timer start en de anonieme functie toevoegt aan de wachtrij, verwijder setTimeout() uit de stack.
  • Verwijder second() uit de stack.
  • Voeg third() toe aan de stack, voer third() uit die 3 naar de console logt, verwijder third() uit de stack.
  • De event loop controleert de wachtrij op eventuele wachtende berichten en vindt de anonieme functie van setTimeout(), voegt de functie toe aan de stack die 2 naar de console logt, en verwijdert deze vervolgens uit de stack.

Het gebruik van setTimeout, een asynchrone Web API, introduceert het concept van de wachtrij, waar deze tutorial vervolgens op zal ingaan.

Wachtrij

De wachtrij, ook wel berichtenwachtrij of taakwachtrij genoemd, is een wachtgebied voor functies. Telkens wanneer de call stack leeg is, zal de event loop de wachtrij controleren op eventuele wachtende berichten, te beginnen bij het oudste bericht. Zodra het er een vindt, zal het deze toevoegen aan de stack, die de functie in het bericht zal uitvoeren.

In het setTimeout voorbeeld wordt de anonieme functie direct uitgevoerd na de rest van de uitvoering op het hoogste niveau, omdat de timer is ingesteld op 0 seconden. Het is belangrijk om te onthouden dat de timer niet betekent dat de code exact na 0 seconden of na de opgegeven tijd zal worden uitgevoerd, maar dat het de anonieme functie na die tijd aan de wachtrij zal toevoegen. Dit wachtrij-systeem bestaat omdat als de timer de anonieme functie direct aan de stapel zou toevoegen wanneer de timer eindigt, dit de uitvoering van de huidige functie zou onderbreken, wat onbedoelde en onvoorspelbare effecten zou kunnen hebben.

Opmerking: Er is ook nog een andere wachtrij genaamd de taakwachtrij of microtaakwachtrij die beloften afhandelt. Microtaken zoals beloften worden met een hogere prioriteit afgehandeld dan macrotaken zoals setTimeout.

Nu weet je hoe de gebeurtenis-lus de stapel en de wachtrij gebruikt om de uitvoervolgorde van code te beheren. De volgende taak is om uit te zoeken hoe je de uitvoervolgorde in je code kunt beheersen. Om dit te doen, zul je eerst leren over de originele manier om ervoor te zorgen dat asynchrone code correct wordt afgehandeld door de gebeurtenis-lus: callback-functies.

Callback-functies

In het setTimeout-voorbeeld werd de functie met de time-out uitgevoerd na alles in de belangrijkste top-level uitvoeringscontext. Maar als je ervoor wilde zorgen dat een van de functies, zoals de derde functie, na de time-out werd uitgevoerd, dan moest je asynchrone codeermethoden gebruiken. De time-out hier kan een asynchrone API-oproep voorstellen die gegevens bevat. Je wilt werken met de gegevens van de API-oproep, maar je moet ervoor zorgen dat de gegevens eerst worden geretourneerd.

De originele oplossing voor het omgaan met dit probleem is het gebruik van terugbel-functies. Terugbel-functies hebben geen speciale syntaxis; het zijn gewoon functies die als argument aan een andere functie zijn doorgegeven. De functie die een andere functie als argument aanneemt, wordt een hogere-orde functie genoemd. Volgens deze definitie kan elke functie een terugbel-functie worden als deze als argument wordt doorgegeven. Terugbellen zijn van nature niet asynchroon, maar kunnen worden gebruikt voor asynchrone doeleinden.

Hier is een syntactisch codevoorbeeld van een hogere-orde functie en een terugbel-functie:

// Een functie
function fn() {
  console.log('Just a function')
}

// Een functie die een andere functie als argument aanneemt
function higherOrderFunction(callback) {
  // Wanneer je een functie aanroept die als argument is doorgegeven, wordt dit een terugbeling genoemd
  callback()
}

// Het doorgeven van een functie
higherOrderFunction(fn)

In deze code definieer je een functie fn, definieer je een functie hogereOrdeFunctie die een functie terugbeling als argument aanneemt, en geef je fn door als een terugbeling aan hogereOrdeFunctie.

Het uitvoeren van deze code zal het volgende opleveren:

Output
Just a function

Laten we teruggaan naar de eerste, tweede en derde functies met setTimeout. Dit is wat je tot nu toe hebt:

function first() {
  console.log(1)
}

function second() {
  setTimeout(() => {
    console.log(2)
  }, 0)
}

function third() {
  console.log(3)
}

De taak is om de uitvoering van de derde functie altijd te vertragen totdat de asynchrone actie in de tweede functie is voltooid. Hier komen callbacks van pas. In plaats van de eerste, tweede en derde op het topniveau van uitvoering uit te voeren, geef je de derde functie door als een argument aan tweede. De tweede functie zal de callback uitvoeren nadat de asynchrone actie is voltooid.

Hier zijn de drie functies met een callback toegepast:

// Definieer drie functies
function first() {
  console.log(1)
}

function second(callback) {
  setTimeout(() => {
    console.log(2)

    // Voer de callback-functie uit
    callback()
  }, 0)
}

function third() {
  console.log(3)
}

Voer nu eerste en tweede uit, en geef vervolgens derde als argument door aan tweede:

first()
second(third)

Na het uitvoeren van dit codeblok ontvang je de volgende uitvoer:

Output
1 2 3

Eerst zal 1 worden afgedrukt, en nadat de timer is voltooid (in dit geval nul seconden, maar je kunt dit naar believen wijzigen) zal het 2 en vervolgens 3 afdrukken. Door een functie als een callback door te geven, heb je met succes de uitvoering van de functie vertraagd totdat de asynchrone Web API (setTimeout) is voltooid.

Het belangrijkste punt hier is dat callbackfuncties niet asynchroon zijn—setTimeout is de asynchrone Web API die verantwoordelijk is voor het afhandelen van asynchrone taken. De callbackfunctie geeft je alleen de mogelijkheid om te worden geïnformeerd wanneer een asynchrone taak is voltooid en behandelt het succes of falen van de taak.

Nu je hebt geleerd hoe je callbacks kunt gebruiken om asynchrone taken af te handelen, legt de volgende sectie de problemen uit van het te veel nesten van callbacks en het creëren van een “pyramide van ellende.”

Geneste Callbacks en de Pyramide van Ellende

Callbackfuncties zijn een effectieve manier om uitgestelde uitvoering van een functie te verzekeren totdat een andere is voltooid en terugkeert met gegevens. Echter, vanwege de geneste aard van callbacks, kan de code rommelig worden als je veel opeenvolgende asynchrone verzoeken hebt die van elkaar afhankelijk zijn. Dit was een grote frustratie voor JavaScript-ontwikkelaars in het begin, en als gevolg hiervan wordt code die geneste callbacks bevat vaak de “pyramide van ellende” of “callbackhel” genoemd.

Hier is een demonstratie van geneste callbacks:

function pyramidOfDoom() {
  setTimeout(() => {
    console.log(1)
    setTimeout(() => {
      console.log(2)
      setTimeout(() => {
        console.log(3)
      }, 500)
    }, 2000)
  }, 1000)
}

In deze code is elke nieuwe setTimeout genest binnen een hogere-ordefunctie, waardoor een piramidevorm van steeds dieper geneste callbacks ontstaat. Als je deze code uitvoert, krijg je het volgende:

Output
1 2 3

In de praktijk kan dit veel ingewikkelder worden met echte asynchrone code. Waarschijnlijk moet je foutafhandeling doen in asynchrone code, en vervolgens wat gegevens doorgeven van elke respons naar het volgende verzoek. Als je dit met terugbellen doet, zal je code moeilijk te lezen en te onderhouden zijn.

Hier is een uitvoerbaar voorbeeld van een meer realistische “pyramide van ellende” waar je mee kunt experimenteren:

// Voorbeeld asynchrone functie
function asynchronousRequest(args, callback) {
  // Gooi een fout als er geen argumenten worden doorgegeven
  if (!args) {
    return callback(new Error('Whoa! Something went wrong.'))
  } else {
    return setTimeout(
      // Voeg gewoon een willekeurig nummer toe zodat het lijkt alsof de geconstrueerde asynchrone functie
      // verschillende gegevens teruggeeft
      () => callback(null, {body: args + ' ' + Math.floor(Math.random() * 10)}),
      500,
    )
  }
}

// Geneste asynchrone verzoeken
function callbackHell() {
  asynchronousRequest('First', function first(error, response) {
    if (error) {
      console.log(error)
      return
    }
    console.log(response.body)
    asynchronousRequest('Second', function second(error, response) {
      if (error) {
        console.log(error)
        return
      }
      console.log(response.body)
      asynchronousRequest(null, function third(error, response) {
        if (error) {
          console.log(error)
          return
        }
        console.log(response.body)
      })
    })
  })
}

// Uitvoeren
callbackHell()

In deze code moet je elke functie laten omgaan met een mogelijke response en een mogelijke error, wat de functie callbackHell visueel verwarrend maakt.

Het uitvoeren van deze code zal het volgende opleveren:

Output
First 9 Second 3 Error: Whoa! Something went wrong. at asynchronousRequest (<anonymous>:4:21) at second (<anonymous>:29:7) at <anonymous>:9:13

Deze manier van omgaan met asynchrone code is moeilijk te volgen. Als gevolg hiervan werd het concept van promises geïntroduceerd in ES6. Dit is het onderwerp van het volgende gedeelte.

Beloftes

A promise represents the completion of an asynchronous function. It is an object that might return a value in the future. It accomplishes the same basic goal as a callback function, but with many additional features and a more readable syntax. As a JavaScript developer, you will likely spend more time consuming promises than creating them, as it is usually asynchronous Web APIs that return a promise for the developer to consume. This tutorial will show you how to do both.

Het maken van een belofte

Je kunt een belofte initialiseren met de syntax new Promise, en je moet deze initialiseren met een functie. De functie die aan een belofte wordt doorgegeven, heeft de parameters resolve en reject. De functies resolve en reject behandelen respectievelijk het succes en het falen van een operatie.

Schrijf de volgende regel om een belofte te declareren:

// Initialiseer een belofte
const promise = new Promise((resolve, reject) => {})

Als je de geïnitialiseerde belofte in deze staat inspecteert met de console van je webbrowser, zul je zien dat deze de status pending heeft en een waarde van undefined:

Output
__proto__: Promise [[PromiseStatus]]: "pending" [[PromiseValue]]: undefined

Tot nu toe is er niets ingesteld voor de belofte, dus deze zal daar voor altijd in een pending-status blijven staan. Het eerste wat je kunt doen om een belofte uit te proberen, is de belofte vervullen door deze op te lossen met een waarde:

const promise = new Promise((resolve, reject) => {
  resolve('We did it!')
})

Nu, bij inspectie van de belofte, zul je merken dat deze de status fulfilled heeft en een w aarde ingesteld op de waarde die je aan resolve hebt doorgegeven:

Output
__proto__: Promise [[PromiseStatus]]: "fulfilled" [[PromiseValue]]: "We did it!"

Zoals aan het begin van dit gedeelte vermeld, is een belofte een object dat een waarde kan teruggeven. Na succesvol te zijn vervuld, wordt de w aarde van undefined naar gevulde gegevens gewijzigd.

A promise can have three possible states: pending, fulfilled, and rejected.

  • In afwachting – Initieel staat vóór het worden opgelost of verworpen
  • Vervuld – Succesvolle operatie, belofte is vervuld
  • Afgewezen – Mislukte operatie, belofte is afgewezen

Na vervuld of afgewezen te zijn, is een belofte afgehandeld.

Nu je een idee hebt van hoe beloftes worden gemaakt, laten we eens kijken hoe een ontwikkelaar deze beloftes kan gebruiken.

Het verbruiken van een Belofte

De belofte in de laatste sectie is vervuld met een waarde, maar je wilt ook in staat zijn om toegang te krijgen tot de waarde. Beloftes hebben een methode genaamd then die zal worden uitgevoerd nadat een belofte resolve heeft bereikt in de code. then zal de waarde van de belofte teruggeven als een parameter.

Dit is hoe je de w aarde van de voorbeeldbelofte zou retourneren en loggen:

promise.then((response) => {
  console.log(response)
})

De belofte die je hebt gemaakt had een [[PromiseValue]] van We hebben het gedaan!. Deze waarde is wat in de anonieme functie zal worden doorgegeven als respons:

Output
We did it!

Tot nu toe was het voorbeeld dat je hebt gemaakt niet betrokken bij een asynchrone Web API – het legde alleen uit hoe je een native JavaScript-belofte kunt maken, oplossen en consumeren. Met behulp van setTimeout kun je een asynchrone aanvraag testen.

De volgende code simuleert gegevens die worden geretourneerd vanuit een asynchrone aanvraag als een belofte:

const promise = new Promise((resolve, reject) => {
  setTimeout(() => resolve('Resolving an asynchronous request!'), 2000)
})

// Log het resultaat
promise.then((response) => {
  console.log(response)
})

Het gebruik van de then-syntax zorgt ervoor dat de response alleen gelogd wordt wanneer de setTimeout-operatie is voltooid na 2000 milliseconden. Dit alles gebeurt zonder het nesten van callbacks.

Nu, na twee seconden, zal het de belofte waarde oplossen en zal het gelogd worden in then:

Output
Resolving an asynchronous request!

Beloftes kunnen ook geketend worden om gegevens door te geven aan meer dan één asynchrone operatie. Als er een waarde wordt geretourneerd in then, kan er een andere then worden toegevoegd die vervult zal worden met de geretourneerde waarde van de vorige then:

// Keten een belofte
promise
  .then((firstResponse) => {
    // Geef een nieuwe waarde terug voor de volgende then
    return firstResponse + ' And chaining!'
  })
  .then((secondResponse) => {
    console.log(secondResponse)
  })

De vervulde respons in de tweede then zal de retourwaarde loggen:

Output
Resolving an asynchronous request! And chaining!

Aangezien then geketend kan worden, maakt het de consumptie van beloftes mogelijk om meer synchroon te lijken dan callbacks, aangezien ze niet genest hoeven te worden. Dit maakt het mogelijk om meer leesbare code te schrijven die gemakkelijker te onderhouden en te verifiëren is.

Foutafhandeling

Tot nu toe heb je alleen een belofte afgehandeld met een succesvolle resolve, die de belofte in een vervulde toestand plaatst. Maar vaak moet je ook een fout afhandelen bij een asynchrone aanvraag – als de API down is, of als er een verkeerde of ongeautoriseerde aanvraag wordt verzonden. Een belofte zou beide gevallen moeten kunnen afhandelen. In dit gedeelte maak je een functie om zowel het succes- als het foutgeval van het maken en verbruiken van een belofte uit te testen.

Deze getUsers-functie zal een vlag doorgeven aan een belofte, en de belofte retourneren:

function getUsers(onSuccess) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // Behandel resolve en reject in de asynchrone API
    }, 1000)
  })
}

Stel de code zo op dat als onSuccess true is, de timeout vervuld zal worden met wat data. Als false zal de functie worden afgewezen met een fout:

function getUsers(onSuccess) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // Behandel resolve en reject in de asynchrone API
      if (onSuccess) {
        resolve([
          {id: 1, name: 'Jerry'},
          {id: 2, name: 'Elaine'},
          {id: 3, name: 'George'},
        ])
      } else {
        reject('Failed to fetch data!')
      }
    }, 1000)
  })
}

Voor het succesvolle resultaat retourneer je JavaScript-objecten die voorbeeldgegevens van gebruikers voorstellen.

Om de fout af te handelen, gebruik je de catch-instantiemethode. Dit geeft je een foutafhandelingscallback met de error als parameter.

Voer het getUser-commando uit met onSuccess ingesteld op false, gebruikmakend van de then-methode voor het succesgeval en de catch-methode voor de fout:

// Voer de getUsers-functie uit met de valse vlag om een fout te veroorzaken
getUsers(false)
  .then((response) => {
    console.log(response)
  })
  .catch((error) => {
    console.error(error)
  })

Sinds de fout is veroorzaakt, zal de then-clausule worden overgeslagen en zal de catch de fout afhandelen:

Output
Failed to fetch data!

Als je de vlag omschakelt en in plaats daarvan resolve gebruikt, zal de catch worden genegeerd en zal de data worden geretourneerd:

// Voer de getUsers-functie uit met de true-vlag om succesvol op te lossen
getUsers(true)
  .then((response) => {
    console.log(response)
  })
  .catch((error) => {
    console.error(error)
  })

Dit zal de gebruikersgegevens opleveren:

Output
(3) [{…}, {…}, {…}] 0: {id: 1, name: "Jerry"} 1: {id: 2, name: "Elaine"} 3: {id: 3, name: "George"}

Ter referentie, hier is een tabel met de handler-methoden op Promise-objecten:

Method Description
then() Handles a resolve. Returns a promise, and calls onFulfilled function asynchronously
catch() Handles a reject. Returns a promise, and calls onRejected function asynchronously
finally() Called when a promise is settled. Returns a promise, and calls onFinally function asynchronously

Beloften kunnen verwarrend zijn, zowel voor nieuwe ontwikkelaars als voor ervaren programmeurs die nog nooit in een asynchrone omgeving hebben gewerkt. Zoals echter vermeld, is het veel gebruikelijker om beloften te consumeren dan ze te maken. Meestal levert een browser’s Web API of een externe bibliotheek de belofte en hoef je deze alleen maar te consumeren.

In het laatste gedeelte over beloften zal deze tutorial een veelvoorkomend gebruiksscenario van een Web API die beloften retourneert aanhalen: de Fetch API.

Het gebruik van de Fetch API met Beloften

Een van de meest nuttige en vaak gebruikte Web API’s die een belofte retourneert, is de Fetch API, waarmee je een asynchrone resource-aanvraag over een netwerk kunt maken. fetch is een tweedelig proces en vereist daarom het koppelen van then. Dit voorbeeld demonstreert het gebruik van de GitHub API om gegevens van een gebruiker op te halen, terwijl ook eventuele potentiële fouten worden afgehandeld:

// Haal een gebruiker op van de GitHub API
fetch('https://api.github.com/users/octocat')
  .then((response) => {
    return response.json()
  })
  .then((data) => {
    console.log(data)
  })
  .catch((error) => {
    console.error(error)
  })

De fetch-aanvraag wordt verzonden naar de URL https://api.github.com/users/octocat, die asynchroon wacht op een antwoord. De eerste then geeft het antwoord door aan een anonieme functie die het antwoord opmaakt als JSON-gegevens, en geeft vervolgens de JSON door aan een tweede then die de gegevens naar de console logt. De catch-verklaring logt eventuele fouten naar de console.

Het uitvoeren van deze code zal het volgende opleveren:

Output
login: "octocat", id: 583231, avatar_url: "https://avatars3.githubusercontent.com/u/583231?v=4" blog: "https://github.blog" company: "@github" followers: 3203 ...

Dit zijn de opgevraagde gegevens van https://api.github.com/users/octocat, weergegeven in JSON-indeling.

Deze sectie van de tutorial liet zien dat beloftes veel verbeteringen bevatten voor het omgaan met asynchrone code. Maar, hoewel het gebruik van then om asynchrone acties te behandelen gemakkelijker te volgen is dan de piramide van terugroepingen, geven sommige ontwikkelaars nog steeds de voorkeur aan een synchrone opmaak voor het schrijven van asynchrone code. Om aan deze behoefte tegemoet te komen, introduceerde ECMAScript 2016 (ES7) async-functies en het await-trefwoord om het werken met beloftes gemakkelijker te maken.

Async Functies met async/await

Een async functie stelt je in staat om asynchrone code te behandelen op een manier die synchroon lijkt. async functies gebruiken nog steeds promises onder de motorkap, maar hebben een meer traditionele JavaScript-syntax. In deze sectie ga je voorbeelden van deze syntax uitproberen.

Je kunt een async functie maken door het async trefwoord toe te voegen vóór een functie:

// Maak een async functie
async function getUser() {
  return {}
}

Hoewel deze functie nog niets asynchroon afhandelt, gedraagt het zich anders dan een traditionele functie. Als je de functie uitvoert, zul je merken dat het een promise retourneert met een [[PromiseStatus]] en [[PromiseValue]] in plaats van een retourwaarde.

Probeer dit uit door een oproep naar de getUser functie te loggen:

console.log(getUser())

Dit zal het volgende geven:

Output
__proto__: Promise [[PromiseStatus]]: "fulfilled" [[PromiseValue]]: Object

Dit betekent dat je een async functie kunt behandelen met then op dezelfde manier als je een promise zou kunnen behandelen. Probeer dit uit met de volgende code:

getUser().then((response) => console.log(response))

Deze oproep naar getUser geeft de retourwaarde door aan een anonieme functie die de waarde naar de console logt.

Je zult het volgende ontvangen wanneer je dit programma uitvoert:

Output
{}

Een async-functie kan een promise die binnenin wordt aangeroepen afhandelen met behulp van de await-operator. await kan worden gebruikt binnen een async-functie en zal wachten totdat een promise is afgehandeld voordat de aangewezen code wordt uitgevoerd.

Met deze kennis kunt u de Fetch-aanvraag uit het vorige gedeelte herschrijven met async/await als volgt:

// Behandel fetch met async/await
async function getUser() {
  const response = await fetch('https://api.github.com/users/octocat')
  const data = await response.json()

  console.log(data)
}

// Voer async-functie uit
getUser()

De await-operators zorgen ervoor dat de data niet wordt gelogd voordat het verzoek deze met gegevens heeft gevuld.

Nu kan de uiteindelijke data worden afgehandeld binnen de getUser-functie, zonder dat er gebruik hoeft te worden gemaakt van then. Dit is de uitvoer van het loggen van data:

Output
login: "octocat", id: 583231, avatar_url: "https://avatars3.githubusercontent.com/u/583231?v=4" blog: "https://github.blog" company: "@github" followers: 3203 ...

Let op: In veel omgevingen is async nodig om await te gebruiken — echter, sommige nieuwe versies van browsers en Node staan top-level await toe, waardoor je de noodzaak om een async-functie te maken om de await in te wikkelen, kunt omzeilen.

Tenslotte, aangezien u de vervulde promise binnen de asynchrone functie afhandelt, kunt u ook de fout afhandelen binnen de functie. In plaats van de catch-methode te gebruiken met then, zult u het try/catch-patroon gebruiken om de uitzondering af te handelen.

Voeg de volgende gemarkeerde code toe:

// Succes en fouten afhandelen met async/await
async function getUser() {
  try {
    // Succes afhandelen in try
    const response = await fetch('https://api.github.com/users/octocat')
    const data = await response.json()

    console.log(data)
  } catch (error) {
    // Fout afhandelen in catch
    console.error(error)
  }
}

Het programma zal nu naar het catch-blok springen als het een fout ontvangt en die fout naar de console loggen.

Moderne asynchrone JavaScript-code wordt meestal behandeld met de syntaxis async/await, maar het is belangrijk om een werkende kennis te hebben van hoe promises werken, vooral omdat promises in staat zijn tot extra functies die niet kunnen worden afgehandeld met async/await, zoals het combineren van promises met Promise.all().

Opmerking: async/await kan worden gereproduceerd door generators in combinatie met promises te gebruiken om meer flexibiliteit aan uw code toe te voegen. Om meer te leren, bekijk onze zelfstudie Begrip Generators in JavaScript.

Conclusie

Omdat Web API’s vaak gegevens asynchroon leveren, is het leren omgaan met het resultaat van asynchrone acties een essentieel onderdeel van het zijn van een JavaScript-ontwikkelaar. In dit artikel heb je geleerd hoe de hostomgeving de gebeurtenislus gebruikt om de uitvoeringsvolgorde van code te behandelen met de stack en queue. Je hebt ook voorbeelden geprobeerd van drie manieren om het succes of falen van een asynchrone gebeurtenis af te handelen, met terugroepingen, beloftes en async/await-syntax. Ten slotte heb je de Fetch Web API gebruikt om asynchrone acties af te handelen.

Voor meer informatie over hoe de browser parallelle gebeurtenissen afhandelt, lees Concurrentiemodel en de gebeurtenislus op het Mozilla Developer Network. Als je meer wilt leren over JavaScript, keer dan terug naar onze Hoe te coderen in JavaScript serie.

Source:
https://www.digitalocean.com/community/tutorials/understanding-the-event-loop-callbacks-promises-and-async-await-in-javascript