Begripsvermogen van JavaScript-beloften

Inleiding

Javascript Promises kunnen lastig te begrijpen zijn. Daarom wil ik de manier opschrijven waarop ik promises begrijp.

Promises Begrijpen

Een Promise in het kort:

“Stel je bent een kind. Je moeder belooft je dat ze je volgende week een nieuw telefoon zal kopen.”

Je weet niet of je die telefoon krijgt tot volgende week. Je moeder kan echt je een nieuwe telefoon kopen, of dat doet ze niet.

Dat is een belofte. Een belofte heeft drie staten. Ze zijn:

  1. Pending: Je weet niet of je die telefoon krijgt
  2. Fulfilled: Moeder is blij, ze koopt je een nieuwe telefoon
  3. Rejected: Moeder is onblij, ze koopt je geen telefoon

Een Promise Maken

Laten we dit omzetten naar JavaScript.

// ES5: Deel 1

var isMomHappy = false;

// Promise
var willIGetNewPhone = new Promise(
    function (resolve, reject) {
        if (isMomHappy) {
            var phone = {
                brand: 'Samsung',
                color: 'black'
            };
            resolve(phone); // vervuld
        } else {
            var reason = new Error('mom is not happy');
            reject(reason); // afwijzen
        }

    }
);

De code is zelf al vrij expressief.

Hieronder ziet een promise-syntax er normaal gesproken uit:

// promise-syntax ziet er zo uit
new Promise(function (resolve, reject) { ... } );

Consumeren van Promises

Nu we de promise hebben, laten we het consumeren:

// ES5: Deel 2

var willIGetNewPhone = ... // vervolg van deel 1

// roep onze promise aan
var askMom = function () {
    willIGetNewPhone
        .then(function (fulfilled) {
            // jaaay, je hebt een nieuwe telefoon
            console.log(fulfilled);
             // uitvoer: { merk: 'Samsung', kleur: 'zwart' }
        })
        .catch(function (error) {
            // oeps, mama heeft het niet gekocht
            console.log(error.message);
             // uitvoer: 'mama is niet blij'
        });
};

askMom();

Laten we het voorbeeld uitvoeren en het resultaat bekijken!

Demo: https://jsbin.com/nifocu/1/edit?js,console

Keten van Promises

Promises zijn ketensbaar.

Stel je voor, jij, het kind, beloofd je vriend dat je hun laat zien het nieuwe telefoon wanneer je moeder je er een koopt.

Dat is een andere belofte. Laten we het opschrijven!

// ES5

// 2e belofte
var showOff = function (phone) {
    return new Promise(
        function (resolve, reject) {
            var message = 'Hey friend, I have a new ' +
                phone.color + ' ' + phone.brand + ' phone';

            resolve(message);
        }
    );
};

Opmerking: We kunnen de bovenstaande code korter schrijven door het als volgt te doen:

// verkort het

// 2e belofte
var showOff = function (phone) {
    var message = 'Hey friend, I have a new ' +
                phone.color + ' ' + phone.brand + ' phone';

    return Promise.resolve(message);
};

Laten we de promises ketenen. Jij, het kind, kunt pas de showOff belofte starten na de willIGetNewPhone belofte.

// roep onze belofte aan
var askMom = function () {
    willIGetNewPhone
    .then(showOff) // keten het hier
    .then(function (fulfilled) {
            console.log(fulfilled);
         // uitvoer: 'Hé vriend, ik heb een nieuwe zwarte Samsung telefoon.'
        })
        .catch(function (error) {
            // oeps, moeder koopt het niet
            console.log(error.message);
         // uitvoer: 'moeder is niet blij'
        });
};

Zo kun je de belofte ketenen.

Beloften zijn Asynchroon

Beloften zijn asynchroon. Laten we een bericht loggen voor en na het aanroepen van de belofte.

// roep onze belofte aan
var askMom = function () {
    console.log('before asking Mom'); // log voor
    willIGetNewPhone
        .then(showOff)
        .then(function (fulfilled) {
            console.log(fulfilled);
        })
        .catch(function (error) {
            console.log(error.message);
        });
    console.log('after asking mom'); // log na
}

Wat is de verwachte uitvoerreeks? Je zou verwachten:

1. before asking Mom
2. Hey friend, I have a new black Samsung phone.
3. after asking mom

Echter, de werkelijke uitvoerreeks is:

1. before asking Mom
2. after asking mom
3. Hey friend, I have a new black Samsung phone.

Je zou niet stoppen met spelen terwijl je wacht op de belofte van je moeder (de nieuwe telefoon). Dat noemen we asynchroon: de code zal uitvoeren zonder te blokkeren of te wachten op het resultaat. Alles wat moet wachten op een belofte om verder te gaan, wordt in .then geplaatst.

Hier is het volledige voorbeeld in ES5:

// ES5: Volledig voorbeeld

var isMomHappy = true;

// Promise
var willIGetNewPhone = new Promise(
    function (resolve, reject) {
        if (isMomHappy) {
            var phone = {
                brand: 'Samsung',
                color: 'black'
            };
            resolve(phone); // gehaald
        } else {
            var reason = new Error('mom is not happy');
            reject(reason); // afgewezen
        }

    }
);

// 2e promise
var showOff = function (phone) {
    var message = 'Hey friend, I have a new ' +
                phone.color + ' ' + phone.brand + ' phone';

    return Promise.resolve(message);
};

// roep onze promise aan
var askMom = function () {
    willIGetNewPhone
    .then(showOff) // ketting hier
    .then(function (fulfilled) {
            console.log(fulfilled);
            // uitvoer: 'Hey vriend, Ik heb een nieuwe zwarte Samsung telefoon.'
        })
        .catch(function (error) {
            // oeps, moeder krijgt hem niet
            console.log(error.message);
            // uitvoer: 'Moeder is niet blij'
        });
};

askMom();

Promessen in ES5, ES6/2015, ES7/Next

ES5 – Meerderheid browsers

Hetdemo-code werkt in ES5-omgevingen (alle grote browsers + NodeJs) als u Bluebirdpromessenbibliotheek toevoegt. Dit omdat ES5 geen ondersteuning voorpromessen uit de box biedt. Een andere beroemdepromessenbibliotheek is Q door Kris Kowal.

ES6 / ES2015 – Moderne browsers, NodeJs v6

De demo code werkt direct uit de doos omdat ES6 promises native ondersteunt. Daarnaast kunnen we met ES6-functies de code verder vereenvoudigen met een pijlfunctie en const en let gebruiken.

Hier is het volledige voorbeeld in ES6-code:

//_ ES6: Volledig voorbeeld_

const isMomHappy = true;

// Promise
const willIGetNewPhone = new Promise(
    (resolve, reject) => { // pijlfunctie
        if (isMomHappy) {
            const phone = {
                brand: 'Samsung',
                color: 'black'
            };
            resolve(phone);
        } else {
            const reason = new Error('mom is not happy');
            reject(reason);
        }

    }
);

// 2e promise
const showOff = function (phone) {
    const message = 'Hey friend, I have a new ' +
                phone.color + ' ' + phone.brand + ' phone';
    return Promise.resolve(message);
};

// roep onze promise aan
const askMom = function () {
    willIGetNewPhone
        .then(showOff)
        .then(fulfilled => console.log(fulfilled)) // pijlfunctie
        .catch(error => console.log(error.message)); // pijlfunctie
};

askMom();

Merk op dat alle var zijn vervangen door const. Alle function(resolve, reject) zijn vereenvoudigd tot (resolve, reject) =>. Er zijn een aantal voordelen die voortkomen uit deze wijzigingen.

ES7 – Async/Await

ES7 introduceerde de async en await syntaxis. Het maakt de asynchrone syntaxis gemakkelijker te begrijpen, zonder de .then en .catch.

Schrijf ons voorbeeld opnieuw met ES7-syntaxis:

// ES7: Volledig voorbeeld
const isMomHappy = true;

// Promise
const willIGetNewPhone = new Promise(
    (resolve, reject) => {
        if (isMomHappy) {
            const phone = {
                brand: 'Samsung',
                color: 'black'
            };
            resolve(phone);
        } else {
            const reason = new Error('mom is not happy');
            reject(reason);
        }

    }
);

// 2e promise
async function showOff(phone) {
    return new Promise(
        (resolve, reject) => {
            var message = 'Hey friend, I have a new ' +
                phone.color + ' ' + phone.brand + ' phone';

            resolve(message);
        }
    );
};

// roep onze promise aan in ES7 async await stijl
async function askMom() {
    try {
        console.log('before asking Mom');

        let phone = await willIGetNewPhone;
        let message = await showOff(phone);

        console.log(message);
        console.log('after asking mom');
    }
    catch (error) {
        console.log(error.message);
    }
}

// async await het hier ook
(async () => {
    await askMom();
})();

Beloftes en Wanneer Ze Te Gebruiken

Waarom hebben we beloftes nodig? Hoe zag de wereld eruit voordat we beloftes hadden? Voordat we deze vragen beantwoorden, laten we teruggaan naar de basis.

Normale Functie VS Asynchrone Functie

Laten we eens kijken naar deze twee voorbeelden. Beide voorbeelden voeren de optelling van twee getallen uit: één voegt toe met normale functies en de ander voegt extern toe.

Normale Functie om Twee Getallen Toe Te Voegen

// twee getallen normaal optellen

function add (num1, num2) {
    return num1 + num2;
}

const result = add(1, 2); // je krijgt result = 3 onmiddellijk
Asynchrone Functie om Twee Getallen Toe Te Voegen
// twee getallen extern optellen

// het resultaat krijgen door een API aan te roepen
const result = getAddResultFromServer('http://www.example.com?num1=1&num2=2');
// je krijgt result = "undefined"

Als je de getallen met de normale functie optelt, krijg je het resultaat onmiddellijk. Als je echter een externe oproep doet om het resultaat te krijgen, moet je wachten en kun je het resultaat niet onmiddellijk krijgen.

Je kunt niet weten of je de resultaat zal krijgen omdat de server mogelijk down zijn, langzaam responsief zijn, etc. Je wilt niet dat je hele proces blokkeerd wordt terwijl je wacht op het resultaat.

API’s aanroepen, bestanden downloaden en bestanden lezen zijn onder andere gewoonlijk eenvoudige asynchrone operationele die je uitvoert.

Je hoeft geen promeses te gebruiken voor een asynchrone aanroep. Voor het gebruik van promeses waren callback functies een functie waar je bij het ontvangen van het resultaat aanroept. Laat ons de vorige voorbeeld modificeren om een callback te accepteren.

// remote addition of two numbers
// get the result by calling an API

function addAsync (num1, num2, callback) {
    // use the famous jQuery getJSON callback API
    return $.getJSON('http://www.example.com', {
        num1: num1,
        num2: num2
    }, callback);
}

addAsync(1, 2, success => {
    // callback
    const result = success; // you get result = 3 here
});

// Subsequent Async Action

In plaats van de cijfers te voeg aan elkaar in een normale functie, wil je drie keer toevoegen. In een normale functie zou je dit doen:

// add two numbers normally

let resultA, resultB, resultC;

 function add (num1, num2) {
    return num1 + num2;
}

resultA = add(1, 2); // you get resultA = 3 immediately
resultB = add(resultA, 3); // you get resultB = 6 immediately
resultC = add(resultB, 4); // you get resultC = 10 immediately

console.log('total' + resultC);
console.log(resultA, resultB, resultC);

Zo ziet dit eruit met callbacks:

// twee getallen op afstand optellen
// het resultaat ophalen via een API-oproep

let resultA, resultB, resultC;

function addAsync (num1, num2, callback) {
    // gebruik de beroemde jQuery getJSON callback API
	// https://api.jquery.com/jQuery.getJSON/
    return $.getJSON('http://www.example.com', {
        num1: num1,
        num2: num2
    }, callback);
}

addAsync(1, 2, success => {
    // callback 1
    resultA = success; // hier krijg je result = 3

    addAsync(resultA, 3, success => {
        // callback 2
        resultB = success; // hier krijg je result = 6

        addAsync(resultB, 4, success => {
            // callback 3
            resultC = success; // hier krijg je result = 10

            console.log('total' + resultC);
            console.log(resultA, resultB, resultC);
        });
    });
});

Demo: https://jsbin.com/barimo/edit?html,js,console

Deze syntaxis is minder gebruiksvriendelijk vanwege de diep geneste callbacks.

Diep geneste callbacks vermijden

Promises kunnen helpen om diep geneste callbacks te vermijden. Laten we eens kijken naar de promise-versie van hetzelfde voorbeeld:

// twee getallen op afstand optellen met behulp van observable

let resultA, resultB, resultC;

function addAsync(num1, num2) {
    // gebruik de ES6 fetch API, die een promise retourneert
	// Wat is .json()? https://developer.mozilla.org/en-US/docs/Web/API/Body/json
    return fetch(`http://www.example.com?num1=${num1}&num2=${num2}`)
        .then(x => x.json()); 
}

addAsync(1, 2)
    .then(success => {
        resultA = success;
        return resultA;
    })
    .then(success => addAsync(success, 3))
    .then(success => {
        resultB = success;
        return resultB;
    })
    .then(success => addAsync(success, 4))
    .then(success => {
        resultC = success;
        return resultC;
    })
    .then(success => {
        console.log('total: ' + success)
        console.log(resultA, resultB, resultC)
    });

Met promises vegen we de callback af met .then. Op een manier ziet het er schoner uit omdat er geen callback nesting is. Met ES7 async syntaxis kun je dit voorbeeld verder verbeteren.

Observables

Voordat je besluit om met promises verder te gaan, is er iets dat is ontstaan om je te helpen met async data, genaamd Observables.

Laten we naar hetzelfde voorbeeld kijken, geschreven met Observables. In dit voorbeeld zullen we RxJS gebruiken voor de observables.

let Observable = Rx.Observable;
let resultA, resultB, resultC;

function addAsync(num1, num2) {
    // gebruik de ES6 fetch API, die een promise retourneert
    const promise = fetch(`http://www.example.com?num1=${num1}&num2=${num2}`)
        .then(x => x.json());

    return Observable.fromPromise(promise);
}

addAsync(1,2)
  .do(x => resultA = x)
  .flatMap(x => addAsync(x, 3))
  .do(x => resultB = x)
  .flatMap(x => addAsync(x, 4))
  .do(x => resultC = x)
  .subscribe(x => {
    console.log('total: ' + x)
    console.log(resultA, resultB, resultC)
  });

Observables kunnen meer interessante dingen doen. Bijvoorbeeld, delay een functie toevoegen door 3 seconden met slechts één regel code of retry, zodat je een oproep een bepaald aantal keren kunt herhalen.

...

addAsync(1,2)
  .delay(3000) // 3 seconden vertragen
  .do(x => resultA = x)
  ...

Je kunt een van mijn RxJs-berichten hier.

Conclusie

Vertrouwd raken met callbacks en promises is belangrijk. Begrijp ze en gebruik ze. Maak je nog geen zorgen over Observables. Alle drie kunnen een rol spelen in je ontwikkeling, afhankelijk van de situatie.

Source:
https://www.digitalocean.com/community/tutorials/understanding-javascript-promises