Asynchronous Programming - marissaverdonck/weekly-nerd-1920 GitHub Wiki
Asynchronous Programming
Iets wat ik na 10 weken meesterproef nog steeds erg lastig vind, zijn asynchrone functies. Met wat hulp lukt het me deze functies toe te passen, maar hoe het nu echt werkt blijft ingewikkeld. Hoogste tijd om dit eens goed te onderzoeken!
Kenmerken van Javascript
JavaScript heeft twee hoofdkenmerken als programmeertaal. Ten eerste is het synchroon wat betekent dat de code regel na regel wordt uitgevoerd. Ten tweede dat deze single-threaded is, er wordt één commando tegelijk uitgevoerd.
In het volgende voorbeeld wordt een functie geactiveerd nadat de gebruiker klikt op de button. JavaScript plaatst de taak in de wachtrij maar zal eerst uitvoeren wat er nog aanwezig was in de huidige wachtrij. Nadat het klaar is met de oproepen die daar aanwezig zijn, wordt nu het verzoek van de addEventListener() uitgevoerd. Als taken in de wachtrij worden geplaatst, kunnen ze asynchroon worden uitgevoerd waarna ze terug gaan naar de hoofdthread. Er wordt wel slechts één stuk code worden uitgevoerd.
const button = document.querySelector('button');
button.addEventListener('click', function(e) {
console.log('user click just happened!');
})
Een thread is een ander lopend programma. Maar aangezien de meeste moderne computers meerdere processors bevatten, kunnen zelfs meerdere threads tegelijkertijd op verschillende processors worden uitgevoerd. Een tweede thread zou het tweede verzoek kunnen starten en vervolgens wachten beide threads tot hun resultaten terugkomen, waarna ze opnieuw synchroniseren om hun resultaten te combineren.
In dit voorbeeld wordt de functie console.log(request.responseText)
pas uitgevoerd nadat de request "ready" is.
var request = new XMLHttpRequest();
request.open('GET', '//some.api.at/server', true);
// observe for server response
request.onreadystatechange = function() {
if (request.readyState === 4 && request.status === 200) {
console.log(request.responseText);
}
}
request.send();
Asychrone code reageert op een externe gebeurtenis die buiten ons bereik plaats vindt. Bijvoorbeeld een tijdsinterval, een gebruikersactie of een serverreactie. Deze manier van code wordt daarom ook wel het Observer Pattern genoemd.
Callbacks
Callbacks zijn functies die worden aangeroepen wanneer de vorige acties zijn voltooid. Met asynchroon programmeren kunnen functies die een langzame actie uitvoeren, een extra argument meegegeven worden, een callback-functie. Wanneer een actie is voltooid, wordt de callback-functie aangeroepen met het resultaat.
Het uitvoeren van meerdere asynchrone acties achter elkaar met callbacks betekent dat nieuwe functies moeten blijven worden doorgeven om de voortzetting van de berekening na de acties af te handelen.
import {bigOak} from "./crow-tech";
bigOak.readStorage("food caches", caches => {
let firstCache = caches[0];
bigOak.readStorage(firstCache, info => {
console.log(info);
});
});
Met deze stijl van programmeren kom je steeds in een andere functie terecht. Het terugroepen van een callback is fout gevoeliger dan het retourneren van een waarde. Om gecompliceerde dingen te doen, zoals meerdere acties tegelijkertijd uitvoeren, kan een beetje lastig worden.
Promises
Een promise is een asynchrone actie die op een bepaald moment kan worden voltooid en een waarde oplevert. Het kan andere functies op de hoogte stellen wanneer de waarde beschikbaar is.
De makkelijkste manier om een promise te gebruiken, is met promise.resolve
. Deze functie zorgt ervoor dat de waarde die eraan meegegeven wordt, in een promise is verpakt.
let fifteen = Promise.resolve(15);
fifteen.then(value => console.log(`Got ${value}`));
// Got 15
Het is handig om promises te zien als een apparaat om code asynchrone mee te maken. Een normale waarde is er gewoon, een promised waarde is een waarde die er misschien al is of op in de toekomst kan verschijnen.
Om een promise te maken gebruik je een constructor. De constructor verwacht een functie als argument die direct wordt aangeroepen wordt gebruikt om de promise op te lossen.
function storage(nest, name) {
return new Promise(resolve => {
nest.readStorage(name, result => resolve(result));
});
}
storage(bigOak, "enemies")
.then(value => console.log("Got", value));
Het belangrijkste voordeel van promises zijn dat ze het gebruik van asynchrone functies vereenvoudigen. In plaats van callbacks telkens door te geven, lijken promises op gewone functies: ze nemen input als argumenten en geven hun output terug. Het enige verschil is dat de output mogelijk nog niet beschikbaar is.
Failures
Een netwerkaanvraag kan mislukken of een code die deel uitmaakt van de asynchrone berekening kan een fout opleveren. Je wil dat deze fouten opgespoord en gerapporteerd worden. Een veel gebruikte conventie is dat het eerste argument voor de callback wordt gebruikt om aan te geven dat de actie is mislukt, en de tweede bevat de waarde die wordt geproduceerd door de actie wanneer deze succesvol was.
Promises maken dit gemakkelijker. Het argument resolve
wordt gebruikt als de actie is met succes is voltooid, of reject
wanneer deze is mislukt. Resolve-handlers zoals .then
worden aangeroepen als de actie is geslaagd.
Net zoals het oplossen van een belofte een waarde biedt, biedt het afwijzen er ook een, meestal de reden van de afwijzing genoemd. De reject zal doorgegeven worden aan de volgende promise die een afgewezen promise creëert. Promises hebben hier de catch
methode voor.
Methodes als .catch en .then worden gezien als een pijplijn waardoor asynchrone waarden of fouten worden verplaatst
new Promise((_, reject) => reject(new Error("Fail")))
.then(value => console.log("Handler 1"))
.catch(reason => {
console.log("Caught failure " + reason);
return "nothing";
})
.then(value => console.log("Handler 2", value));
// → Caught failure Error: Fail
// → Handler 2 nothing
Meerdere promises
Wanneer er meerdere promises zijn die tegelijkertijd worden uitgevoerd, kan de functie promise.all
nuttig zijn. Het retourneert een promise die wacht tot alle beloften in de array zijn opgelost. Vervolgens wordt de promise omgezet in een array met de waarden die deze beloften hebben opgeleverd. Wanneer er 1 promise is de collectie mislukt, mag natuurlijk niet de hele promise.all mislukken. Daarom krijgen de waardes uit elke promise een true of false mee.
promise.all([
loadImage('images/cat1.jpg'),
loadImage('images/cat2.jpg'),
loadImage('images/cat3.jpg')
]).then((images) => {
images.forEach(img => addImg(img.src))
}).catch((error) => {
console.log("Unexpected failure:", error)
})
Async
Binnen een async functie wachten de stappen met await, tot dat de stap klaar is. Vanaf dat moment kan de functie weer verder met het uitvoeren van de volgende stap.
Zie het volgende voorbeeld. Elke keer dat de async functie een await
statement vindt, stopt deze met uitvoeren totdat de waarde of promise is opgelost. Er wordt gewacht op de promise, de functie wordt bevroren en op een later tijdstip hervat.
const { mkdir, writeFile, readFile } = require('fs').promises;
const less = require('less')
async function processLess() {
const content = await readFile('./main.less', 'utf-8')
const result = await less.render(content)
await mkdir('./assets')
await writeFile('assets/main.css', result.css, 'utf-8')
}
processLess()
Wanneer een de async functie of methode wordt aangeroepen, levert dat een promise op. Zodra het een iets teruggeeft, is die promise opgelost. Als er een failure wordt gevonden, wordt de promise afgewezen. try
en catch
kunnen deze fouten opsporen. Bij een catch wordt de failure gemeld aan de console. Bij try kan de functie verder met het proces.
const { mkdir, writeFile, readFile } = require('fs').promises;
const less = require('less');
async function processLess() {
try {
const content = await readFile('./main.less', 'utf-8')
const result = await less.render(content)
await mkdir('./assets')
await writeFile('assets/main.css', result.css, 'utf-8')
} catch(e) {
console.error(e)
}
}
processLess()
Bronnen:
Haverbeke, M. Eloquent Javascript (3e editie) Chapter 11: Asynchronous Programming. Geraadpleegd van: https://eloquentjavascript.net/11_async.html
Menichelli, J. (2019, 25 oktober) Writing Asynchronous Tasks In Modern JavaScript Geraadpleegd van: https://www.smashingmagazine.com/2019/10/asynchronous-tasks-modern-javascript/
Fun Fun Function. (2015, 31 augustus) Promises. Geraadpleegd van: https://www.youtube.com/watch?v=2d7s3spWAzo&list=PL0zVEGEvSaeEd9hlmCXrk5yUyqUag-n84
Fun Fun Function. (2017, 14 augustus) Async / await in JavaScript - What, Why and How. Geraadpleegd van: https://www.youtube.com/watch?v=568g8hxJJp4