JavaScript Callback of Promise

Als je nu https://www.facebook.com opent, zie je dat de pagina meteen laadt. Je eigen foto linksboven is zichtbaar, de zoekbalk bovenaan het scherm en het menu aan de linkerkant. In de seconden daarna verschijnen langzaam de berichten op de tijdlijn, niet allemaal, maar alleen in het gedeelte van het scherm dat zichtbaar is. Als je nu naar beneden scrolt, zie je dat ook de oudere berichten pas op dat moment geladen worden.

Facebook laadt deze onderdelen na elkaar om er voor te zorgen dat de pagina snel zichtbaar is. Als Facebook zou wachten met het tonen van de pagina totdat alle gegevens en berichten geladen zijn, zou het zeker een aantal seconden duren voordat de Facebook pagina zichtbaar is. En volgens studies is het zo dat hoe langer het duurt dat een website laadt, hoe meer gebruikers afhaken.

Asynchroon programmeren

Met asynchroon programmeren voorkom je dat de code moet wachten op uitvoer voordat het verder kan. Bij Asynchroon programmeren vindt de communicatie binnen de code plaats met behulp van events en callbacks. Een callback wordt aangeroepen als de data beschikbaar is. Vanaf dat punt kan het programma weer verder.

Op het moment dat er asynchrone code aanwezig is, heeft de ontwikkelaar de keuze tussen het gebruik van Callbacks of Promises. Een Promise is een nieuwer concept. Het voegt een extra laag van abstractie toe aan de code.

In principe is er niets mis met een Callback, maar het wordt lastiger wanneer je te maken krijgt met geneste callbacks. Geneste callbacks ontstaan als er meerdere dingen achter elkaar uitgevoerd moeten worden en waarbij de nieuwe aanroep moet wachten op de vorige aanroep. In de code ontstaat dan de “Pyramid of Doom”. Dit maakt de code moeilijk leesbaar en om die reden kan je dit beter voorkomen.

pyramidOfDoom

Callbacks

Als voorbeeld het laden van een afbeelding. Het resultaat van het load proces vangen we af door callbacks te registreren:

var img1 = document.querySelector('.img-1');

img1.addEventListener('load', function() {
  // Image loaded.
});

img1.addEventListener('error', function() {
  // Image loading failed.
});

In het voorbeeld hierboven, kan het zo zijn dat ‘load’ event al afgevuurd is voordat de we begonnen met luisteren naar het ‘load’ event. In dat geval ontvangen we nooit de melding dat de afbeelding geladen is.

Dit probleem is op te lossen door extra code toe te voegen die kijkt naar de status van img.complete, maar dat maakt de code er niet duidelijker op. Om toch elegante code op te leveren, kunnen we gebruik maken van een Promise object.

Promise

In de praktijk

Een Promise is het object wat je terug krijgt uit een function call wanneer de waarde die de functie moet teruggeven niet direct beschikbaar is, maar je er niet op wilt wachten. Je krijgt dan een Promise object terug waar je een eventhandler aan kunt hangen welke uitgevoerd wordt als de functie klaar is.

Een Promise object kan slechts eenmaal van status wijzigen. Als de Promise de status “resolved” eenmaal heeft, kan deze status niet meer wijzigen.

Eenvoudig voorbeeld

Een voorbeeld van een Promise object in Angular. Een enkele call met success en error afhandeling.

DsrContact.query({relationId: controller.relationId}).$promise
    .then(function (contacts) {
        controller.contacts = contacts;
        
        // Etc.
    })
    .catch(function (response) {
        // Handle error.
    });

Chaining voorbeeld

Een voorbeeld van meerdere calls in serie uitgevoerd. Er is nog steeds maar één catch functie voor de error afhandeling.

DsrContact.query({relationId: controller.relationId}).$promise
    .then(function (contacts) {
        // Todo: Process contacts.
        return contacts;
    })
    .then(function (processedContacts) {
        controller.contacts = processedContacts;
        // Etc.
    })
    .catch(function (response) {
        // Handle error.
    });

Voorbeeld parallelle requesten

In dit laatste voorbeeld worden twee calls gelijktijdig uitgevoerd. Met de $q.all() functie wordt daarna gewacht totdat alle calls succesvol uitgevoerd zijn. Pas daarna wordt de $q.then() functie aangeroepen.

var sitePromise = DsrSite.query({relationId: controller.relationId}).$promise;
var assetPromise = DsrAsset.query({relationId: controller.relationId}).$promise;

// Execute REST calls in parallel.
$q
    .all([sitePromise, assetPromise])
    .then(function (values) {
        // All REST calls successfully.
        controller.sites = values[1];
        controller.assets = values[2];

        // Etc.
    })
    .catch(function (response) {
        // Handle error.
    });

Browser Support

Native Promises worden bij het schrijven van dit artikel inmiddels door alle browsers behalve Internet Explorer, ondersteund. Daarnaast bestaan er libraries als Q die Promises volgens de specificaties implementeren en ook te gebruiken zijn in oudere browsers.

Conclusie

Promises zijn een krachtige aanvulling die het schrijven van asynchrone code makkelijker en overzichtelijker maakt. Daarnaast zijn composities en error afhandeling eleganter te programmeren, de code blijft veel compacter, bevat minder indents en is daardoor veel beter leesbaar dan geneste callback functies.

Resources

Hack de ATAG One Android App

PlayStore-AtagOneApp
De ATAG One app, gemaakt door Applied Micro Electronics AME B.V.

De ATAG One app (voor iPhone en Android) benadert rechtstreeks via het locale netwerk de ATAG One thermostaat. Atag stelt helaas geen technische documentatie online beschikbaar hoe de thermostaat aan te sturen is. De enige manier om uit te vissen hoe de app “praat” met de thermostaat, is door de app te hacken 🙂

Lees verder…

ATAG One thermostaat

ATAG One thermostaat

Sinds een week of twee ben ik in het bezit van een ATAG One thermostaat in combinatie met een nieuwe ATAG E325EC ketel.

Bij de aanschaf heb ik getwijfeld tussen een Nest of deze thermostaat. De Nest ondersteunde een tijdje terug nog geen OpenTherm (sinds deze week de Nest v3 wel) waardoor de ketel niet modulerend aan te sturen is. De ATAG One werkt perfect samen met de ATAG ketel (daar ga ik vanuit). Daarom voor de ATAG One thermostaat gekozen.

Lees verder…

JSConf EU Berlijn – conferentiedag 1

De conferentie begint op zaterdag 13 september. Om 8:30 uur ontbijt. De koffie is vers en voor het ontbijt is er ruime keuze uit allerlei gezonde salades, veel vegetarisch.

De locatie van JSConf EU Berlijn is het Radialsystem V in Berlijn. Dit is een voormalig pompstation dat op dit moment fungeert als cultureel centrum.

Lees verder…

Backbone.js: een korte introductie

In deze post beschrijf ik in het kort de werking van het web framework Backbone.js. Dit is mijn eerste serieuze kennismaking met Backbone.js. Tijdens het schrijven van deze post heb ik een voorbeeld uitgewerkt. Het is een voorbeeld dat ik eerder heb uitgewerkt voor het web framework AngularJS en dat maakt een vergelijking tussen beide mogelijk.

backbone-580

Wat is Backbone.js

Backbone.js is een JavaScript web framework. Het doel van Backbone.js is dezelfde als bijvoorbeeld AngularJS, namelijk structuur geven aan wegapplicaties.

Lees verder…

AngularJS; Lege velden na reload, opgelost!

Verschrikkelijk irritant, dat na een reload van de pagina alle velden van de webapplicatie weer op de initiële waarde staan. Het is veel praktischer als ze nog steeds dezelfde waarde bevatten na de reload. Hieronder beschrijf ik hoe je dit voorelkaar krijgt in het webframework AngularJS.

Lees verder…