n00b pro

03. Asynchrone Javascript

On Youtube

Video: Web API's

https://www.youtube.com/embed/4OFHIhSFNgI

Over web API's

Wat is een web API

Algemeen in programmeren is een API of Application Programming Interface, een set van methodes die je als developer kan gebruiken om informatie uit te wisselen met jouw toepassing.

Een Web API is hetzelfde maar dan op het web, dus een dienst die je kan integreren in jouw webtoepassing. Denk aan:

Web API's vinden

Er zijn massa's dergelijke API's te vinden! Bijna alle grote webtoepassingen als Youtube, Google Maps, Dropbox, Twitter, Flickr enz... hebben er één (zoek via google, bv. "Flickr API"). Kleinere (en vaak makkelijker te gebruiken) API's bestaan ook: weather service, random quotes...

Flickr API documentatie

RapidAPI heeft een uitgebreide collectie op haar website, en je hoeft maar één keer te registreren:

overzicht API's op rapidapi.com

Web API's gebruiken

Hoe je een Web API moet gebruiken varieert enorm, van heel eenvoudig tot zeer moeilijk. Enkele aspecten om rekening mee te houden:

Dog API voorbeeld: open API, geen API key

Een API waar geen registratie of key voor nodig is, heet een open API. Er is geen enkele controle of limiet op het gebruik. Een voorbeeld is de Dog API, waar je informatie kan vinden over honden:

documentatie

dog API documentatie

Zoals je ziet in de documentatie, bestaat deze eenvoudige API uit een set URL's waarmee je informatie kan opvragen, bv.
https://dog.ceo/api/breeds/list/all
https://dog.ceo/api/breeds/image/random
https://dog.ceo/api/breed/schnauzer/images

request en response

Vragen we bijvoorbeeld via https://dog.ceo/api/breed/hound/images/random/5 vijf random foto's op van het ras "hound" (de request), dan krijg je volgende informatie terug (de response):

API request (cfr. adresbalk) en response

JSON

JSON of Javascript Object Notation is technisch gezien de notatie van een object in Javascript

{"message":["https:\/\/images.dog.ceo\/breeds\/hound-afghan\/n02088094_2292.jpg","https:\/\/images.dog.ceo\/breeds\/hound-blood\/n02088466_7091.jpg","https:\/\/images.dog.ceo\/breeds\/hound-blood\/n02088466_7609.jpg","https:\/\/images.dog.ceo\/breeds\/hound-english\/n02089973_208.jpg","https:\/\/images.dog.ceo\/breeds\/hound-ibizan\/n02091244_2820.jpg"],"status":"success"}

Je zou de response zelfs kunnen kopiëren in een Javascript variabele en gebruiken in je pagina:

const data = {
  message: [
    'https://images.dog.ceo/breeds/hound-afghan/n02088094_2292.jpg',
    'https://images.dog.ceo/breeds/hound-blood/n02088466_7091.jpg',
    'https://images.dog.ceo/breeds/hound-blood/n02088466_7609.jpg',
    'https://images.dog.ceo/breeds/hound-english/n02089973_208.jpg',
    'https://images.dog.ceo/breeds/hound-ibizan/n02091244_2820.jpg'
  ],
  status: 'success'
}

Verwerkt in een eenvoudige toepassing:

<!-- HTML -->
<button type="button" id="btnRandomDogs">FETCH DOGS</button>
<div id="dogs"></div>

<!-- CSS -->
<style>
  img {
    margin: 10px;
    height: 200px;
  }
</style>

<!-- SCRIPT -->
<script>
  const btnRandomDogs = document.querySelector('#btnRandomDogs');
  const divDogs = document.querySelector('#dogs');

  function fetchRandomDogs() {
    // data
    const data = {
      message: [
        'https://images.dog.ceo/breeds/hound-afghan/n02088094_2292.jpg',
        'https://images.dog.ceo/breeds/hound-blood/n02088466_7091.jpg',
        'https://images.dog.ceo/breeds/hound-blood/n02088466_7609.jpg',
        'https://images.dog.ceo/breeds/hound-english/n02089973_208.jpg',
        'https://images.dog.ceo/breeds/hound-ibizan/n02091244_2820.jpg'
      ],
      status: 'success'
    };

    // generate images
    if (data.status == 'success') {
      data.message.forEach(url => {
        const img = `<img src="${url}" alt="">`;
        divDogs.innerHTML += img;
      });
    }
  }

  btnRandomDogs.addEventListener('click', function() {
    fetchRandomDogs();
  });
</script>

toepassing 1: 5 random foto's ophalen

Met fetch() kan je zo'n URL dynamisch opvragen en de data gebruiken:

<!-- HTML -->
<button type="button" id="btnRandomDogs">FETCH DOGS</button>
<div id="dogs"></div>

<!-- CSS -->
<style>
  img {
    margin: 10px;
    height: 200px;
  }
</style>

<!-- SCRIPT -->
<script>
  const btnRandomDogs = document.querySelector('#btnRandomDogs');
  const divDogs = document.querySelector('#dogs');

  async function fetchRandomDogs() {
    // build request
    const url = 'https://dog.ceo/api/breed/hound/images/random/5';

    // fetch data
    const resp = await fetch(url);
    if (!resp.ok) {
      console.log('opvragen random dogs mislukt');
      return;
    }
    const data = await resp.json();

    // process data
    data.message.forEach(url => {
      divDogs.innerHTML += `<img src="${url}" alt="">`;
    });
  }

  // start fetching on click
  btnRandomDogs.addEventListener('click', async function() {
    console.log('start feching dogs...');
    await fetchRandomDogs();
    console.log('...fetch finished');
  });
</script>

de uitleg over async/await volgt later nog, maar kort gezegd betekent async: "dit kan even duren, doe alvast verder met de rest", en await: "nee nee, ik wacht wel tot het resultaat binnen is"

toepassing 1: gegevens worden dynamisch opgehaald

toepassing 2: foto's van een specifiek ras ophalen

Een iets uitgebreider voorbeeld, waarbij eerst de hondenrassen opgehaald worden in een lijstje:

<!-- HTML -->
<select id="selBreeds" class="hide"></select>
<p id="parMessage"> </p>
<div id="divImages"></div>

<!-- CSS -->
<style>
  img {
    margin: 10px;
    height: 200px;
  }
</style>

<!-- SCRIPT -->
<script>
  const selBreeds = document.querySelector('#selBreeds');
  const parMessage = document.querySelector('#parMessage');
  const divImages = document.querySelector('#divImages');

  selBreeds.addEventListener('change', async function() {
    // build request
    parMessage.innerHTML = `fetching images for ${this.value}`;
    const url = `https://dog.ceo/api/breed/${this.value}/images`;

    // fetch data
    const resp = await fetch(url);
    const data = await resp.json();
    if (data.status != 'success') {
        console.log('fetching breeds failed');
        return;
    }

    // show photos
    divImages.innerHTML = '';
    data.message.forEach(src => {
        divImages.innerHTML += `<img src="${src}" alt="">`;
    });
    parMessage.innerHTML = `done fetching images for ${this.value}!`;
  });

  async function fetchBreeds() {
    // build request
    const url = 'https://dog.ceo/api/breeds/list/all';

    // fetch data
    const resp = await fetch(url);
    const data = await resp.json();
    if (data.status != 'success') {
        console.log('fetching breeds failed');
        return;
    }

    // populate list of breeds and show list
    selBreeds.innerHTML = `<option value="">selecteer...</option>`;
    for (const breed in data.message) {
        selBreeds.innerHTML += `<option>${breed}</option>`;
    }
    selBreeds.classList.remove('hide');
  }

  async function doFetch() {
    parMessage.innerHTML = 'fetching breeds...';
    await fetchBreeds();
    parMessage.innerHTML = 'done fetching breeds - select one from the list';
  }

  doFetch();
</script>
toepassing 2: eerste fetch() dient om het lijstje op te bouwen, tweede fetch() om foto's op te halen

Flickr API voorbeeld: API key, request parameters

API key aanvragen

Flickr is een foto community met een zeer uitgebreide API. Het verschil met het vorige voorbeeld is dat je (na registratie) een API key moet aanvragen, en die meesturen met elke request. De key is gratis, maar het geeft hen, in tegenstelling tot open API's, wel een beetje controle op je verbruik.

API key aanvraag bij Flickr.com

documentatie

In de uitgebreide documentatie vinden we een methode flickr.photos.search, met alle mogelijke opties. Een aantal van deze opties zullen we meesturen als request parameters.

toepassing: zoeken op foto's

Verwerkt in een eenvoudige toepassing:

<!-- HTML -->
<form action="" id="frmSearch">
  <label>search: <input id="inpSearch" type="text" placeholder="enter keyword (.e.g. Eiffel)"></label>
</form>
<div id="pics"></div>

<!-- CSS -->
<style>
  #pics {
    display: flex;
    flex-flow: row wrap;
  }
  #pics img {
    height: 200px;
    display: block;
    margin-top: 20px;
    margin-right: 20px;
  }
  #search {
    width: 200px;
  }
</style>

<!-- SCRIPT -->
<script>
  const pics = document.querySelector('#pics');
  const frmSearch = document.querySelector('#frmSearch');
  const inpSearch = frmSearch.querySelector('#inpSearch');
  const API_KEY = '60dce28390e0a3...'; // vul je eigen key in!

  async function makeSearch(searchval) {
    // build request
    let url = 'https://api.flickr.com/services/rest/';
    const params = new URLSearchParams();
    params.append('api_key', API_KEY);
    params.append('extras', 'url_m');
    params.append('format', 'json');
    params.append('method', 'flickr.photos.search');
    params.append('per_page', 20);
    params.append('nojsoncallback', 1);
    params.append('text', searchval);
    url += '?' + params.toString();

    // fetch data
    const resp = await fetch(url);
    if (!resp.ok) {
      console.log('opvragen foto\'s mislukt');
      return;
    }
    const data = await resp.json();

    // process data
    console.log(data)
    data.photos.photo.forEach(photo => {
      pics.innerHTML += `<img src="${photo.url_m}" alt="">`;
    });
  }

  frmSearch.addEventListener('submit', async function (e) {
    e.preventDefault();
    pics.innerHTML = '';
    console.log('start fetching photos...');
    await makeSearch(inpSearch.value);
    console.log('...done!');
  });
</script>

Github API voorbeeld: token, request headers

open gebruik — beperkt

Je kan in principe de Github API open gebruiken, zonder API key of token:

<!-- HTML -->
<ul id="lstRepos"></ul>

<!-- SCRIPT -->
<script>
  const lstRepos = document.querySelector('#lstRepos');
  async function getMyRepos() {
    // build request
    const username = 'rogiervdl';
    const url = `https://api.github.com/users/${username}/repos`;

    // fetch data
    const resp = await fetch(url);
    if (!resp.ok) {
      console.log('opvragen github repos mislukt');
      return;
    }
    const data = await resp.json();

    // show repos
    data.forEach(entry => {
      lstRepos.innerHTML += `<li>
        <a href="${entry.html_url}">${entry.full_name}</a>
        &mdash; ${entry.description}</li>`;
    });
  }
  async function doGetRepos() {
    console.log('fetching repos...');
    await getMyRepos();
    console.log('...done');
  }
  doGetRepos();
</script>
ophalen en weergeven repos via Github API

Het aantal requests is voor anonieme gebruikers echter standaard beperkt tot amper 60 per uur. Je kan het je kan het aantal resterende requests terugvinden in de response headers onder Network (klik links op de AJAX request):

aantal requests voor anonieme gebruikers is beperkt tot 60 per uur

met bearer token

Je kan als geregistreerde gebruiker dit optrekken naar 5000 per uur door een token te genereren:

aanvraag Github bearer token

Deze token geef je mee met de headers van je request:

<!-- HTML -->
<ul id="lstRepos"></ul>

<!-- SCRIPT -->
<script>
  const lstRepos = document.querySelector('#lstRepos');
  async function getMyRepos() {
    // build request
    const username = 'rogiervdl';
    const url = `https://api.github.com/users/${username}/repos`;
    const options = {
      headers: {
        'Authorization': 'Bearer ghp_fIWhq1DrL...' // geef hier je eigen token mee!
      }
    };

    // fetch data
    const resp = await fetch(url, options); // voeg options toe als tweede parameter
    if (!resp.ok) {
      console.log('opvragen github repos mislukt');
      return;
    }
    const data = await resp.json();

    // show repos
    data.forEach(entry => {
      lstRepos.innerHTML += `<li>
        <a href="${entry.html_url}">${entry.full_name}</a>
        mdash; ${entry.description}</li>`;
    });
  }
  async function doGetRepos() {
    console.log('fetching repos...');
    await getMyRepos();
    console.log('...done');
  }
  doGetRepos();
</script>

Je krijgt nu 5000 requests per uur:

aantal requests met token is opgetrokken tot 5000 per uur

Rapid API voorbeeld: API key, request headers

registratie en API key aanmaken

Rapid API is niet één API, maar een grote verzameling API's die je kan gebruiken met wisselende gebruiksvoorwaarden. Het grote voordeel is dat je maar één keer een API key hoeft aan te vragen voor alle API's. Maak na registratie een app aan en ontvang je API key:

Rapid API registratie
Rapid API API key aanvragen

toepassing: Currency Exchange API

Bij wijze van voorbeeld testen we de Currency Exchange API. Het biedt twee methodes: listquotes en exchange (panel links) met telkens een code voorbeeld (panel rechts; kies Javascript uit de dropdown).

Het (kleine) verschil met het Flickr voorbeeld is dat je de API key als header, en niet als URL parameter moet meegeven.

<!-- HTML -->
<form id="frmCurrencies" class="hide" >
<p >
  <label for="selFrom" >Van:  </label >
  <select id="selFrom" > </select >
</p >
<p >
  <label for="selTo" >Naar:  </label >
  <select id="selTo" > </select >
</p >
<p >
  <button type="submit" >zoek wisselkoers </button >
</p >
</form >
<p id="parMessage" >opvragen lijst munten... </p >

<!-- CSS -->
<style>
  body, input {
    font-family: Verdana;
    font-size: 14px;
  }
  button, select {
    border: 1px solid #666;
    padding: 3px 5px;
  }
  form > p {
    display: flex;
  }
  form > p label {
    width: 100px;
  }
  form > p:last-child {
    padding-left: 100px;
  }
  .hide {
    visibility: hidden;
  }
</style>

<!-- SCRIPT -->
<script>
  const frmCurrencies = document.querySelector('#frmCurrencies');
  const parMessage = document.querySelector('#parMessage');
  const selFrom = document.querySelector('#selFrom');
  const selTo = document.querySelector('#selTo');
  const fetchOptions = {
    method: 'GET',
    headers: {
        'X-RapidAPI-Key': '8bd0826a8amshbaea...', // vul je eigen key in!
        'X-RapidAPI-Host': 'currency-exchange.p.rapidapi.com'
    }
  };

  // get an array of all available currencies
  async function getAllCurrencies() {
    // build request
    const url = 'https://currency-exchange.p.rapidapi.com/listquotes';

    // fetch
    const resp = await fetch(url, fetchOptions);
    if (!resp.ok) {
        console.log('opvragen lijst munten mislukt');
        return;
    }
    const data = await resp.json();

    // return data
    return data;
  }

  // get the exchange rate for two currencies
  async function getExchangeRate(curr1, curr2) {
    // build request
    const params = new URLSearchParams();
    params.append('from', curr1);
    params.append('to', curr2);
    const url = `https://currency-exchange.p.rapidapi.com/exchange?${params.toString()}`;

    // fetch
    const resp = await fetch(url, fetchOptions);
    if (!resp.ok) {
        console.log('opvragen wisselkoers mislukt');
        return;
    }
    const data = await resp.json();

    // return data
    return data;
  }

  // populate dropdowns
  async function startApp() {
    const currencies = await getAllCurrencies();
    currencies.sort().forEach(curr => {
        selFrom.innerHTML += `<option>${curr}</option>`;
        selTo.innerHTML += `<option>${curr}</option>`;
    });
    frmCurrencies.classList.remove('hide');
    selFrom.value = 'EUR';
    selTo.value = 'USD';
    parMessage.innerHTML = 'selecteer twee munten...';
  }

  // handle form submits
  frmCurrencies.addEventListener('submit', async function(e) {
    e.preventDefault();
    const rate = await getExchangeRate(selFrom.value, selTo.value);
    parMessage.innerHTML = `1 ${selFrom.value} = ${rate} ${selTo.value}`;
  });

  // start your engines
  startApp();

</script>

Async/await

Asynchrone functies

Bij sommige functies — zoals fetch() — weet je niet wanneer ze resultaat geven. Men zegt dat ze asynchroon zijn: de rest van de code wacht niet en loopt ondertussen gewoon verder! Een screenshot:

async/await

Met async ("dit kan even duren, wacht niet op het resultaat") en await ("ik wacht liever op het resultaat") kan je het asynchroon gedrag sturen. Weer een screenshot:

Async / await is in het begin een beetje verwarrend, maar volgende principes kunnen helpen:

Er kunnen situaties zijn waarin het wenselijk is asynchrone functies zonder await op te roepen, bijvoorbeeld als de uitvoering geen gevolgen heeft voor het verdere verloop van de code, of als je meerdere processen in parallel wil laten lopen, maar je ben altijd veilig met volgende regels:

API tester — Postman app

Met de postman app kan je API's testen zonder code. Je kan URL's ingeven, request parameters, headers enz... en de response inspecteren in verschillende formaten.

Een aanrader voor wie in detail de request/responses wil testen.

API's testen met Postman