Ik geef training in frontend tools en stacks als HTML, JavaScript, TypeScript, React, Angular en Vue. Ik schrijf boeken en artikelen over tal van (ICT-) onderwerpen. Ik was lead developer en directeur bij Yindo - Jouw digitale bibliotheek. Je kunt mij volgen op Twitter(@PeterKassenaar).

In dit blog vind je persoonlijke artikelen, die niet noodzakelijk tech-gerelateerd zijn.

Views and expressions are my own.

Terug naar de algemene site.

10november

JavaScript: zelf asynchrone- en callbackfuncties schrijven

In webapps hebben we regelmatig te maken met asynchrone JavaScript-functies. Timing is dan belangrijk. Denk bijvoorbeeld aan Ajax-calls naar een externe server waarvan niet bekend is hoe lang ze duren.

Callbacks

Maar ook zelf zul je wellicht asynchrone functies willen schrijven waarvan op voorhand niet bekend is hoe lang de uitvoering duurt. Het is dan een goed idee om de asynchrone functie een callbackfunctie te laten accepteren als parameter.

Je kunt dan de callbackfunctie uitvoeren op het moment dat de asynchrone functie gereed is. Dit is uiteraard met name van belang als je volgende functie afhankelijk is van het resultaat dat de asynchrone functie retour geeft.

Als je jQuery gebruikt, heb je vast wel eens functies als .show(), .hide() of .slideUp() geschreven. Al deze functies accepteren optioneel een callbackfunctie als laatste parameter. Je weet dan zeker dat de callback pas wordt uitgevoerd als de ‘hoofdfunctie’ klaar is.  Hoe schrijf je zoiets zelf?

HTML-code

We schrijven een eenvoudige pagina met de volgende HTML:

<h1>Async functies en callbacks</h1>
<button id="btnAsync">Async</button>
<div id="divFeedback"></div>
<h2 id="divResult"></h2>

Zoals je ziet is er alleen een knop waarop geklikt kan worden en twee divs waarin het resultaat wordt getoond. We schrijven de volgende code in plain JavaScript, dus zonder een bibliotheek als jQuery te gebruiken. Alle volgende functies worden ook geschreven binnen de event handler die het klikken op de knop afvangt:

document.getElementById('btnAsync').addEventListener('click', function () {
  // …
}

1. De asynchrone functie

De functie die we ‘asynchroon’ laten uitvoeren, heeft twee parameters.

  • Het DOM-element waarin het resultaat van de functie wordt geschreven.
  • Een getal waarvan de waarden van 0 tot en met getal bij elkaar worden opgeteld. Deze waarde wordt ook teruggegeven. Als 10 wordt meegegeven, retourneert de functie 1+2+3+4+5+6+7+8+9+10.

Om hier asynchroniteit te simuleren wrappen we de functie in een statement setTimeout(). We geven een vertraging van 3000ms (3 seconden).

 

function asyncFn(el, num) {
     setTimeout(function () {
         // tel alle getallen van 0 tot num bij elkaar op.
        var totaal = 0;
         for (var i = 0; i <= num; i++) {
             totaal += i;
         }
         document.getElementById(el).innerHTML =
			"Async-functie gereed. Totaal: " + totaal;
         return totaal;
     }, 3000); // simuleer vertraging van 3 seconden.
};

2. De JavaScript-call : FOUT

Om de functie aan te roepen zou je de volgende code kunnen schrijven: je roept de functie asyncFn() aan en het resultaat wordt opgeslagen in een variabele totaal. Vervolgens schrijven we het totaal naar het scherm, in de div divResult.

 // ********* Async zonder callback - foutieve uitvoer
 var totaal = asyncFn('divFeedback', 10); 
document.getElementById(
'divResult').innerHTML = 'Klaar! Het totaal is: ' + totaal;

Dat lijkt goed te gaan, maar het resultaat is niet zoals we gehoopt hadden:

async_img_01

Pas na de drie seconden vertraging van de asynchrone functie wordt het correcte resultaat (55) in de pagina getoond.


async_img_02

3. JavaScript – CORRECT

Om er voor te zorgen dat de asynchrone functie goed verwerkt wordt, moeten we zowel de functie zelf aanpassen als het aanroepende statement.

  • De functie moet gereed worden gemaakt om een callbackfunctie als parameter te ontvangen.
  • In de aanroep moeten we de functie meegeven die we willen uitvoeren nadat de asynchrone functie klaar is.

Dit heen en weer schuiven van functies als parameters bij andere functieaanroepen is een van de krachtige (en in mijn optiek: elegante) eigenschappen van JavaScript.

De asynchrone functie passen we bijvoorbeeld als volgt aan:

function asyncFn(el, num, callback) {
         setTimeout(function () {
         // tel alle getallen van 0 tot num bij elkaar op.
        var totaal = 0;
         for (var i = 0; i <= num; i++) {
             totaal += i;
         }
         document.getElementById(el).innerHTML =
                 "Async-functie gereed. Totaal: " + totaal;
         callback(totaal); // callbackfunctie aanroepen, met juiste resultaat als parameter.
    }, 3000); // simuleer vertraging van 3 seconden.
};
En de aanroep van deze functie wordt daarna als volgt herschreven:
// ******* Async met callback - de correcte manier
asyncFn('divFeedback', 10, function showResult(totaal) {
	document.getElementById('divResult').innerHTML =
            
'Klaar! Het totaal is: ' + totaal; });

Hier is goed te zien dat asyncFn() nu wordt aangeroepen met behalve divFeedback en 10 de callbackfunctie als derde parameter. Deze ontvangt op zijn beurt het berekende resultaat als parameter, vanuit de asyncFn. Nu is het resultaat direct naar wens, zoals de afbeelding laat zien.

async_img_03
Om de user experience te verbeteren is het bovendien natuurlijk een goed idee om de bezoeker een melding te geven dat de app bezig is met rekenen. Dat kunnen we bijvoorbeeld doen door een boodschap als ‘Even geduld a.u.b.’  te schrijven in het meegegeven element:

function asyncFn(el, num, callback) {     
document.getElementById(el).innerHTML =
'even geduld...'
;
     setTimeout(
function
() {
        
// ….
    }, 3000); // simuleer vertraging van 3 seconden. };

4. Optioneel: Werken met benoemde functies

Veel programmeurs die zijn gewend te werken met static/compiled languages als Java of C# worden nog steeds een beetje zenuwachtig als ze dat gegoochel met anonieme JavaScript-functies zien als functieparameters. In dat geval mag ook altijd gewerkt worden met benoemde functies (named functions). De code voor aanroepen van de asynchrone functies ziet er dan bijvoorbeeld als volgt uit.

asyncFn('divFeedback', 100, showResult);
//…


function
showResult (totaal) {
     document.getElementById(
'divResult'
).innerHTML =
            
'Klaar! Het totaal is: ' + totaal; }

Een bijkomend voordeel van het werken met named functions is dat de naam van de functie in de call stack wordt getoond bij het debuggen in de Chrome Dev Tools of andere debugger. Het is makkelijker om fouten op te sporen in een serie benoemde functies dan in een stapel anonieme JavaScript-functies.

Samenvatting

In dit artikel heb je gezien hoe je functies schrijft die asynchrone handelingen uitvoeren. Hier werd dat gesimuleerd met een setTimeout(). In werkelijkheid zul je op die plek bijvoorbeeld een Ajax-call naar een externe server uitvoeren.

Je hebt ook gezien hoe je eigen asynchrone functie een callbackfunctie accepteert als parameter en hoe je vervolgens de functieaanroep goed schrijft. Het is gebruikelijk om de callbackfunctie als laatste parameter in te stellen als je eigen functies schrijft.

Meer weten over JavaScript? Lees Web Development Library – JavaScript voor zelfstudie in JavaScript-syntaxis, functies, objecten en modulair programmeren. Volg je liever een op maat gemaakte JavaScript-training met persoonlijke begeleiding? Neem dan contact op!

Peter Kassenaar
-- 10 november 2014.