TypeScript: Promises
Asynchroon vs synchroon
Synchrone code
Tot nu toe hebben we enkel met synchrone code gewerkt. Dit betekent dat de code die we schrijven, één voor één wordt uitgevoerd. Als we een functie aanroepen, wordt de functie uitgevoerd en wanneer de functie klaar is, wordt de volgende regel code uitgevoerd. Dit is de manier waarop we over het algemeen code schrijven. We schrijven code van boven naar beneden en de code wordt van boven naar beneden uitgevoerd.
Als we deze code uitvoeren, zien we dat de drie regels code één voor één worden uitgevoerd. De eerste regel wordt uitgevoerd, dan de tweede en dan de derde. Dit is synchrone code.
Asynchrone code
Bij asynchrone code is het bovenstaande niet het geval. Bij asynchrone code wordt de code niet één voor één uitgevoerd. In plaats daarvan kan het zijn dat een bepaald stuk code wordt uitgevoerd, terwijl een ander stuk code wordt uitgevoerd. We hebben in principe al eens een voorbeeld gezien van asynchrone code. Dit was bijvoorbeeld het geval bij de setTimeout
functie. We hebben gezien dat we een functie kunnen meegeven aan de setTimeout
functie. Deze functie wordt pas na een bepaalde tijd uitgevoerd. Dit is een voorbeeld van asynchrone code. De code die in de functie staat, wordt niet direct uitgevoerd. In plaats daarvan wordt de code uitgevoerd na een bepaalde tijd.
Dit geeft ons het volgende resultaat:
Je ziet nu dat hoewel de code van "Stap 2" op lijn 2 staat, deze pas na "Stap 3" wordt uitgevoerd. Dit komt omdat de code van "Stap 2" pas na 1 seconde wordt uitgevoerd.
Promises
Een belangrijk mechanisme om asynchrone code te schrijven is het gebruik van Promises
. Een Promise
is een object dat een waarde bevat die pas op een later moment beschikbaar zal zijn. Zoals het engelse woord al aangeeft, is een Promise
een belofte dat de functie die een promise teruggeeft, op een later moment een waarde zal teruggeven.
Een van de meest bekende functies die een Promise
gebruikt is de fetch
functie. Deze functie wordt gebruikt om data op te halen van een server. Alle communicatie tussen je programma en de server moet synchroon gebeuren. Dit komt omdat je niet wil dat je programma wacht tot er een antwoord komt van de server. Zelfs al gaat de communicatie met de server heel snel, ze gaat in vergelijking met de uitvoering van een gewone instructie veel trager.
Verschillende statussen van een Promise
Een Promise
heeft verschillende statussen. Een Promise
kan in één van de volgende statussen zitten:
pending: de
Promise
is nog niet afgerond. We hebben dus nog geen waarde.fulfilled: de
Promise
is afgerond. We hebben nu een waarde.rejected: de
Promise
is afgerond, maar er is een fout opgetreden. We hebben nu een fout.
De Promise
begint altijd in de pending status. Wanneer de Promise
afgerond is, kan deze in de fulfilled status zitten of in de rejected status.
Aanmaken van een Promise
We gaan het gebruik van een Promise
bekijken aan de hand van een voorbeeld. We gaan een Promise
maken die een getal teruggeeft. Deze zal een vermenigvuldiging uitvoeren. We maken een Promise
aan met de new Promise
constructor. Deze constructor heeft als argument een functie die twee argumenten heeft: resolve
en reject
. Deze twee argumenten zijn functies die we kunnen aanroepen om de Promise
te laten veranderen van status. Het type dat de promise teruggeeft zetten we tussen de < >
tekens. In ons geval is dit een number
.
Je ziet dat we hier de resolve
functie aanroepen met de vermenigvuldiging van 2 en 2. Dit betekent dat de Promise
nu in de fulfilled status zit en we dus een waarde hebben.
Then functie
Willen we deze waarde gebruiken, dan moeten we een then
functie aanroepen op de Promise
. Deze functie heeft als argument een functie die als argument de waarde bevat die de Promise
teruggeeft. In ons geval is dit een number
.
De functie die we meegeven aan de then
functie wordt pas uitgevoerd wanneer de Promise
in de fulfilled status zit. Dit betekent dat de functie die we meegeven aan de then
functie pas uitgevoerd wordt wanneer de Promise
afgerond is.
Uiteraard is dit een vreemd voorbeeld omdat de vermenigvuldiging van 2 en 2 heel snel gebeurt. In de praktijk duurt de code die in de Promise
staat veel langer. We kunnen dit eenvoudig simuleren door de resolve
functie pas aan te roepen na een bepaalde tijd. Dit doen we met de setTimeout
functie.
Bij het uitvoeren van deze code ga je zien dat de then
functie pas na 1 seconde wordt uitgevoerd. Dit komt omdat de Promise
pas na 1 seconde in de fulfilled status zit.
Promise als return type
Meestal maken we niet zelf een Promise
aan, maar gebruiken we een functie die een Promise
teruggeeft. Deze functie kan dan als return type Promise
hebben. We breiden ons voorbeeld uit met een functie die een Promise
teruggeeft. Deze functie zal een vermenigvuldiging uitvoeren. We geven de functie een argument mee: number1
en number2
. Deze functie zal de vermenigvuldiging van deze twee getallen teruggeven.
Als je deze functie gewoon aanroept alsof het een normale functie is kan je zien dat deze een Promise
teruggeeft.
Je zal hier als output het volgende krijgen:
Dit betekent dat de Promise
nog niet afgerond is. We kunnen de then
functie aanroepen op deze Promise
om de waarde te gebruiken.
of we kunnen de then
functie meteen aanroepen op de functie.
Then chaining
Willen we bijvoorbeeld de multiply
functie twee keer gebruiken, dan zouden we dit op de volgende manier kunnen doen.
Hoewel dat dit werkt is dit niet de beste manier om dit te doen. Je zal al snel zien als je dit vaker moet doen dat je code heel snel onoverzichtelijk wordt. Gelukkig is er een betere manier om dit te doen. We kunnen de then
functie meerdere keren aanroepen op dezelfde Promise
. Dit noemen we then chaining
. We kunnen dit ook doen met de multiply
functie.
Je ziet hierboven dat als je iets wil uitvoeren nadat de eerste then
functie is uitgevoerd, je dan een nieuwe promise moet teruggeven. Deze nieuwe promise zal dan weer gebruikt worden in de volgende then
functie. Dit is de manier om then chaining
te doen. Zo kan je meerdere multiply functies aan elkaar koppelen. Dit kan je zoveel keren doen als je wil zonder dat je code onoverzichtelijk wordt.
Voor een ander voorbeeld, gaan we de fetch
functie gebruiken. Deze functie wordt gebruikt om data op te halen van een server. We gaan de data van de volgende url opvragen: https://jsonplaceholder.typicode.com/todos/1
. Deze url bevat een todo item. We gaan de titel van dit todo item opvragen.
Zoals we al vermeld hebben is de fetch
functie een functie die een Promise
teruggeeft. Deze Promise
bevat de response van de server. Als de server een response teruggeeft, dan zal de Promise
in de fulfilled status zitten en zal de eerste functie uitgevoerd worden die met de then
functie is meegegeven. Omdat het omzetten van de response naar json ook een asynchrone operatie is, is het nodig om hier ook een then
functie aan toe te voegen. Deze wordt pas uitgevoerd wanneer de response omgezet is naar json. Als laatste wordt de titel van het todo item afgedrukt.
Catch
Zoals we al vermeld hadden kan een Promise
in de rejected status komen te zitten. Dit betekent dat er een fout is opgetreden. Bijvoorbeeld als we onze multiply functie zouden uitbreiden met een foutmelding als het resultaat groter is dan 10.
Willen we deze foutmelding opvangen kunnen we dit niet doen met een try/catch blok maar moeten we een catch functie toevoegen achter de then
functie.
Bij het uitvoeren van deze code zal je zien dat de foutmelding wordt afgedrukt en de rest van de then
functies niet meer worden uitgevoerd.
Async/await
Er bestaat ook een andere manier om met promises te werken. Dit is met behulp van async/await. Eigenlijk is dit gewoon 'syntax sugar' om het gebruik van promises wat overzichtelijker te maken. We hernemen de multiply functie.
In plaats van de then
functie te gebruiken kunnen we ook de await
keyword gebruiken.
Opgelet, je kan de await
keyword enkel gebruiken binnen een functie die de async
keyword gebruikt. Als je dit niet doet zal je een foutmelding krijgen. Dus wil je await
gebruiken, dan moet je de functie ook async
die await gebruikt async maken.
Om aan te tonen dat deze code overzichtelijker is dan de code met then
functies, gaan we de then
functies vervangen door await
keywords.
Je ziet dat de code hierboven veel overzichtelijker is dan de code met then
functies. Eigenlijk is dit gewoon een andere manier om de then
functies te gebruiken.
Werk je met async
functies, dan is het ook mogelijk om try/catch
blokken te gebruiken om fouten op te vangen.
Promise helper functions
Promise.all
De Promise.all
functie wordt gebruikt om meerdere promises tegelijkertijd uit te voeren. Als je bijvoorbeeld 2 promises hebt die je tegelijkertijd wil uitvoeren en vervolgens wil je de resultaten van beide promises gebruiken, dan kan je dit doen met de Promise.all
functie.
Stel dat we een twee functies hebben die een promise teruggeven. De eerste functie haalt de naam van een gebruiker op en de tweede functie haalt de leeftijd van een gebruiker op. En we gaan er vanuit dat deze twee functies tegelijkertijd moeten worden uitgevoerd. De functie die de naam ophaalt duurt 1 seconde en de functie die de leeftijd ophaalt duurt 2 seconden.
Als we deze twee functies willen uitvoeren, dan kunnen we dit doen met de Promise.all
functie. We geven de Promise.all
functie een array mee met de promises die we willen uitvoeren. De Promise.all
functie zal een promise teruggeven die fulfilled zal worden wanneer alle promises uitgevoerd zijn. De Promise.all
functie zal een array teruggeven met de resultaten van de promises.
En de tijd die nodig is om de promises uit te voeren is de langste tijd van alle promises. In dit geval is dat 2 seconden.
We kunnen dit concept ook toepassen met de fetch functie. Stel dat we drie verschillende todo's willen ophalen van een API:
https://jsonplaceholder.typicode.com/todos/1
https://jsonplaceholder.typicode.com/todos/2
https://jsonplaceholder.typicode.com/todos/3
En we willen die calls tegelijkertijd uitvoeren. Dan kunnen we dit ook doen met de Promise.all
functie.
Het resultaat van de Promise.all
functie is een array met de resultaten van de promises. Let op dat je niet vergeet de json()
functie aan te roepen op de response. Anders krijg je een Response object terug en niet de data die je verwacht.
Promise.race
Wil je meerdere promises tegelijkertijd uitvoeren en enkel de waarde hebben die het snelste beschikbaar is dan kan je Promise.race gebruiken. Stel dat je twee functies hebt die allebei de naam van een gebruiker ophalen. De eerste functie duurt 1 seconde en de tweede functie duurt 2 seconden.
Wil je enkel de name gebruiken van de functie die het snelste klaar is, dan gebruiken we hiervoor de Promise.race
functie. Deze zal al na 500 ms uitgevoerd worden omdat de getNameFromCache
functie het snelste klaar is.
Last updated