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

Leave a Reply

Your email address will not be published. Required fields are marked *