arrow-left

Alle pagina's
gitbookPowered by GitBook
1 van 5

Loading...

Loading...

Loading...

Loading...

Loading...

Fetch

Net zoals in de browser is het mogelijk om in Node.js een HTTP request te doen naar een andere server. Dit doe je met de fetch functie. Deze functie heeft als argument een URL. De functie geeft een Promise terug die een Response object bevat. Dit object bevat de data die je terugkrijgt van de server. In TypeScript is het wel belangrijk dat je het type van de data opgeeft die je verwacht terug te krijgen. Je moet dus een interface voorzien die de data beschrijft.

De syntax is grotendeels hetzelfde als in JavaScript. Het enige verschil is dat je het type van de data moet opgeven.

hashtag
Interface

We gaan in dit voorbeeld gebruik maken van de API. Deze API bevat een aantal endpoints die je kan gebruiken om data op te halen. We gaan in dit voorbeeld gebruik maken van de /posts endpoint. Deze endpoint geeft een lijst van gebruikers terug.

Het eerste wat je moet doen is een interface maken die de data beschrijft die je verwacht terug te krijgen. In dit geval is dit een array van objecten. Elk object heeft een userId, id, title en body property. De userId en id property zijn van het type number. De title en body property zijn van het type string.

Je kan deze interface zelf maken, maar je kan ook gebruik maken van een tool zoals om deze automatisch te genereren. Zorg vooral dat de interface correct is en overeenkomt met de data die je verwacht terug te krijgen.

hashtag
Fetch

Nu kunnen we de fetch functie gebruiken om de data op te halen. We geven als argument de URL van de endpoint mee. Omdat de fetch functie een Promise teruggeeft, kunnen we de then functie gebruiken om de data te gebruiken. Omdat over het algemeen de data die je terugkrijgt van een server een JSON object is, moeten we de data eerst omzetten naar een JavaScript object. Dit doen we met de json functie. Deze functie geeft ook een Promise terug. We kunnen dus de then functie gebruiken om de data te gebruiken.

Stel dat we de titel van de eerste post willen loggen naar de console. We kunnen dit doen met de volgende code:

of met async en await:

Let op dat een API niet altijd een array teruggeeft. Het kan ook een object zijn dat op zijn beurt weer een array bevat. Je moet dus altijd controleren wat de data is die je terugkrijgt.

hashtag
Error afhandelen

De catch functie is nodig om een error af te handelen. Onder een errors vallen alleen errors die veroorzaakt worden op netwerk niveau. Dus bijvoorbeeld als de server niet bereikbaar is of als de URL niet bestaat.

Als je toch een error wil afhandelen die veroorzaakt wordt door een fout in de code van de server, dan moet je de status code van de response controleren. Als de status code 2xx is, dan is er geen error. Als de status code iets anders is, dan is er een error.

We kijken hier na of de status code niet 2xx is aan de hand van de ok property. Deze property is true als de status code 2xx is. Als de status code niet 2xx is, dan gooien we een error.

Deze code is ook weer sterk te vereenvoudigen met async en await:

Je kan ook de status property gebruiken om de status code op te vragen. Deze property bevat een nummer.

Async/Await

Naast de then en catch functies kan je ook gebruik maken van de async en await keywords. Deze keywords zorgen ervoor dat je code eruit ziet alsof het synchroon is, maar dat het eigenlijk asynchroon is. Over het algemeen wordt het gebruik van async en await aangeraden boven het gebruik van then en catch omdat het de code leesbaarder maakt.

We grijpen terug naar het voorbeeld van de multiply functie:

Stel je voor dat we eerst de getallen 2 en 2 willen vermenigvuldigen en daarna het resultaat willen vermenigvuldigen met 5. We kunnen dit doen met de then functie:

Dit valt nog mee, maar als we nog een vermenigvuldiging willen uitvoeren wordt het al snel onleesbaar:

Dit probleem noemen we ook wel de callback hell. Om dit probleem op te lossen kunnen we gebruik maken van async en await. We maken de functie waarin we de vermenigvuldigingen willen uitvoeren async. We kunnen dan de await keyword gebruiken om te wachten tot de Promise is afgerond.

JSONPlaceholderarrow-up-right
QuickTypearrow-up-right

Let wel op dat als je deze code plaatst in de globale scope, je een error zal krijgen. Dit komt omdat je de await keyword enkel kan gebruiken in een async functie. Je kan dit oplossen door de code in een async functie te plaatsen of door de code in een IIFE (Immediately Invoked Function Expression) te plaatsen.

Ook de catch functie kan je vervangen door een try catch blok.

hashtag
Voorbeeld

Het is nu mogelijk om complexe logica te schrijven zonder dat je code totaal onleesbaar wordt. Stel je voor dat je twee getallen wil uitlezen uit een bestand getal1.txt en getal2.txt. Vervolgens wil je een vermenigvuldiging uitvoeren en het resultaat wegschrijven naar een bestand resultaat.txt.

Dit zou er met promises als volgt uitzien:

Dit kan met async en await als volgt:

Zo zie je dat de code veel leesbaarder is geworden en veel minder indentatie heeft.

function multiply(number1: number, number2: number): Promise<number> {
    return new Promise<number>((resolve, reject) => {
        setTimeout(() => {
            resolve(number1 * number2);
        }, 1000);
    });
};
[
  {
    "userId": 1,
    "id": 1,
    "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
    "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
  },
  {
    "userId": 1,
    "id": 2,
    "title": "qui est esse",
    "body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"
  },
  ...
  {
    "userId": 10,
    "id": 100,
    "title": "at nam consequatur ea labore ea harum",
    "body": "cupiditate quo est a modi nesciunt soluta\nipsa voluptas error itaque dicta in\nautem qui minus magnam et distinctio eum\naccusamus ratione error aut"
  }
]
interface Post {
    userId: number;
    id: number;
    title: string;
    body: string;
}
fetch('https://jsonplaceholder.typicode.com/posts')
    .then((response) => response.json())
    .then((response: Post[]) => {
        console.log(response[0].title);
    }).catch((error) => {
        console.log(error);
    });
(async function () {
    try {
        const response = await fetch('https://jsonplaceholder.typicode.com/posts');
        const posts : Post[] = await response.json();
        console.log(posts[0].title);
    } catch (error: any) {
        console.log(error);
    }
})();
fetch('https://jsonplaceholder.typicode.com/posts/123')
    .then(r => {
        if (!r.ok) throw new Error(r.status)
        return r.json()
    })
    .then(r => console.log(r))
    .catch(e => console.log(e));
(async function () {
    try {
        const response = await fetch('https://jsonplaceholder.typicode.com/posts/123');
        if (!response.ok) throw new Error(response.status);
        const posts : Post[] = await response.json();
        console.log(posts[0].title);
    } catch (error: any) {
        console.log(error);
    }
})();
(async function () {
    try {
        const response = await fetch('https://jsonplaceholder.typicode.com/posts/123');
        
        if (response.status === 404) throw new Error('Not found');
        if (response.status === 500) throw new Error('Internal server error');

        const posts : Post[] = await response.json();
        console.log(posts[0].title);
    } catch (error: any) {
        console.log(error);
    }
})();
multiply(2, 2).then((result) => {
    multiply(result, 5).then((result) => {
        console.log(result);
    });
});
multiply(2, 2).then((result) => {
    multiply(result, 5).then((result) => {
        multiply(result, 10).then((result) => {
            console.log(result);
        });
    });
});
let result : number = await multiply(2, 2);
result = await multiply(result, 5);
result = await multiply(result, 10);
console.log(result);
async function main() {
    let result : number = await multiply(2, 2);
    result = await multiply(result, 5);
    result = await multiply(result, 10);
    console.log(result);
}
main();
(async () => {
    let result : number = await multiply(2, 2);
    result = await multiply(result, 5);
    result = await multiply(result, 10);
    console.log(result);
})();
try {
    let result : number = await multiply(2, 2);
    result = await multiply(result, 5);
    result = await multiply(result, 10);
    console.log(result);
} catch (error) {
    console.log(error);
}
import { readFile, writeFile } from "fs/promises";

readFile("getal1.txt", "utf-8").then((getal1) => {
    readFile("getal2.txt", "utf-8").then((getal2) => {
        multiply(parseInt(getal1), parseInt(getal2)).then((result) => {
            writeFile("resultaat.txt", result.toString(), "utf-8").then(() => {
                console.log("Done");
            });
        });
    });
});
import { readFile, writeFile } from "fs/promises";

async function main() {
    try {
        const getal1 = await readFile("getal1.txt", "utf-8");
        const getal2 = await readFile("getal2.txt", "utf-8");
        const result = await multiply(parseInt(getal1), parseInt(getal2));
        await writeFile("resultaat.txt", result.toString(), "utf-8");
        console.log("Done");
    } catch (error) {
        console.log(error);
    }
}

Asynchroon Programmeren

Promises

Een belangrijk mechanisme om asynchrone code te schrijven is het gebruik van Promises. Een Promise is een object dat een waarde bevat die pas op een later moment beschikbaar zal zijn. Zoals het engelse woord al aangeeft, is een Promise een belofte dat de functie die een promise teruggeeft, op een later moment een waarde zal teruggeven.

Een van de meest bekende functies die een Promise gebruikt is de fetch functie. Deze functie wordt gebruikt om data op te halen van een server. Alle communicatie tussen je programma en de server moet asynchroon gebeuren. Dit komt omdat je niet wil dat je programma wacht tot er een antwoord komt van de server. Zelfs al gaat de communicatie met de server heel snel, ze gaat in vergelijking met de uitvoering van een gewone instructie veel trager.

hashtag
Aanmaken van een Promise

We gaan het gebruik van een Promise bekijken aan de hand van een voorbeeld. We gaan een Promise maken die een getal teruggeeft. Deze zal een vermenigvuldiging uitvoeren. We maken een Promise aan met de new Promise constructor. Deze constructor heeft als argument een functie die twee argumenten heeft: resolve en reject. Deze twee argumenten zijn functies die we kunnen aanroepen om de Promise te laten veranderen van status. Het type dat de promise teruggeeft zetten we tussen de < > tekens. In ons geval is dit een number.

Je ziet dat we hier een setTimeout functie gebruiken om de Promise na 1 seconde te laten de promise te resolven. De resolve functie wordt aangeroepen met de waarde die de Promise zal teruggeven. In dit geval is dit het getal 4.

Het gebruiken van de promise gebeurt net zoals in JavaScript met de then functie. Deze functie heeft als argument een functie die de waarde van de promise zal ontvangen. Deze functie wordt pas uitgevoerd als de promise resolved is.

Het datatype van het result argument is het type dat we hebben opgegeven bij het aanmaken van de promise. In dit geval is dit een number. Je mag het type ook weglaten. TypeScript zal dan zelf het type bepalen aan de hand van het type dat je hebt opgegeven bij het aanmaken van de promise.

hashtag
Promise als return type

Meestal maken we niet zelf een Promise aan, maar gebruiken we een functie die een Promise teruggeeft. Deze functie kan dan als return type Promise hebben. We breiden ons voorbeeld uit met een functie die een Promise teruggeeft. Deze functie zal een vermenigvuldiging uitvoeren. We geven de functie een argument mee: number1 en number2. Deze functie zal de vermenigvuldiging van deze twee getallen teruggeven.

Als je deze functie gewoon aanroept alsof het een normale functie is kan je zien dat deze een Promise teruggeeft.

Je zal hier als output het volgende krijgen:

Dit betekent dat de Promise nog niet afgerond is. We kunnen de then functie aanroepen op deze Promise om de waarde te gebruiken.

of we kunnen de then functie meteen aanroepen op de functie.

hashtag
Catch

Als je zelf een promise maakt, dan kan je ook een reject functie aanroepen. Deze functie heeft als argument een error object. Dit object kan je zelf aanmaken. Het is een goed idee om een error object te maken dat een boodschap bevat die uitlegt wat er fout is gegaan.

Als je een reject functie aanroept, dan zal de then functie niet uitgevoerd worden. Je kan een catch functie aanroepen om de fout af te handelen. Deze functie heeft als argument een functie die het error object zal ontvangen.

hashtag
Promise All

Als je meerdere promises hebt die je tegelijkertijd wil uitvoeren, dan kan je de Promise.all functie gebruiken. Deze functie heeft als argument een array van promises. Deze functie zal een nieuwe promise teruggeven die resolved is als alle promises in de array resolved zijn. De waarde die deze promise teruggeeft is een array van de waarden van de promises.

Het resultaat hier is dus:

hashtag
Built-in promises

hashtag
Filesystem

Toegang tot het filesysteem is iets dat over het algemeen traag gaat, dus is het ook een ideaal voorbeeld van asynchrone code. De fs module heeft een aantal functies die je kan gebruiken om bestanden te lezen en te schrijven. Deze functies hebben een variant die promises gebruikt. Deze functies kan je importeren uit de fs/promises module.

Deze functie zal de inhoud van het bestand test.txt inlezen en teruggeven als een string. Als er een fout optreedt, dan zal de catch functie uitgevoerd worden. Bijvoorbeeld als het bestand niet bestaat.

hashtag
DNS lookup

Nog een voorbeeld van een ingebouwde promise is de lookup functie van de dns module. Deze functie zal een IP adres teruggeven als een string. Als er een fout optreedt, dan zal de catch functie uitgevoerd worden.

Extra voorbeelden

Dit onderdeel bevat een aantal extra voorbeelden van asynchroon programmeren in TypeScript. Deze voorbeelden zijn niet noodzakelijk om te kennen, maar kunnen je wel helpen om een beter inzicht te krijgen in asynchroon programmeren en het omgaan met complexere situaties.

hashtag
fetch met paging

We gaan nu een iets complexer voorbeeld bekijken. We gaan deze keer eens de https://reqres.in/api/users API gebruiken. Als je naar de response kijkt, dan zie je dat deze geen array teruggeeft, maar een object. Dit object bevat een array met de key data en ook een andere properties zoals page

,
per_page
,
total
en
total_pages
. Het data object bevat een array van gebruikers.

Hier kan je de volgende interface voor gebruiken:

We gaan nu de gebruikers ophalen en de gebruikers te loggen naar de console.

Merk op dat we hier wel geen rekening hebben gehouden met de pagina's. Als je naar de response kijkt zie je dat hier een page property in zit. Deze property geeft aan op welke pagina je zit. Als je naar de URL kijkt, dan zie je dat er een page query parameter in zit. Deze parameter geeft aan op welke pagina je zit. Als je deze parameter aanpast, dan krijg je een andere pagina terug. Je zou hier dus een loop kunnen maken die alle pagina's afgaat.

Let wel op dat elk paging systeem anders is. Je moet dus altijd controleren hoe het paging systeem werkt.

interface User {
    id: number;
    email: string;
    first_name: string;
    last_name: string;
    avatar: string;
}

interface RootObject {
    page: number;
    per_page: number;
    total: number;
    total_pages: number;
    data: User[];
}
const promise = new Promise<number>((resolve, reject) => {
    setTimeout(() => {
        resolve(2*2);
    }, 1000);
});
promise.then((result : number) => {
    console.log(result);
});
function multiply(number1: number, number2: number): Promise<number> {
    return new Promise<number>((resolve, reject) => {
        setTimeout(() => {
            resolve(number1 * number2);
        }, 1000);
    });
};
const result = multiply(2, 2);
console.log(result); 
Promise { <pending> }
const result = multiply(2, 2);
result.then((result) => {
    console.log(result);
});
multiply(2, 2).then((result) => {
    console.log(result);
});
function multiply(number1: number, number2: number): Promise<number> {
    return new Promise<number>((resolve, reject) => {
        setTimeout(() => {
            if (number1 * number2 > 10) {
                reject(new Error("Result is greater than 10"));
            } else {
                resolve(number1 * number2);
            }
        }, 1000);
    });
};
multiply(4,10)
    .then((result) => console.log(result))
    .catch((error) => console.log(error.message))
Promise.all([multiply(2, 2), multiply(3, 3), multiply(4, 4)])
    .then((results: number[]) => console.log(results))
    .catch((error) => console.log(error.message));
[4, 9, 16]
import { readFile } from "fs/promises";

readFile("test.txt", "utf-8")
    .then((result: string) => console.log(result))
    .catch((error) => console.log(error.message));
import { lookup } from 'dns/promises';

lookup('ap.be')
    .then((result) => console.log(result))
    .catch((error) => console.error(error));
async function main() {
    try {
        const response = await fetch('https://reqres.in/api/users');
        if (!response.ok) throw new Error(response.statusText);
        const users : RootObject = await response.json();
        for (const user of users.data) {
            console.log(user.first_name);
        }
    } catch (error: any) {
        console.log(error);
    }
};
main();
async function main() {
    try {
        let page : number = 1;
        let total_pages : number = 1;

        while (page <= total_pages) {
            const response = await fetch("https://reqres.in/api/users?page=" + page);
            if (!response.ok) throw new Error(response.statusText);
            const root : RootObject = await response.json();
            total_pages = root.total_pages;
            console.log("Page " + page);
            for (const user of root.data) {
                console.log(user.first_name);
            }
            page++;
        }
    } catch (error: any) {
        console.log(error);
    }
};
main();