Een beginnershandleiding voor asynchrone JavaScript

javascript
beloften
AsyncAwait
Een beginnershandleiding voor asynchrone JavaScript cover image

Als u nog maar net begint met programmeren, is de kans groot dat u programma's beschouwt als een reeks opeenvolgende logicablokken, waarbij elk blok iets specifieks doet en het resultaat ervan doorgeeft, zodat het volgende blok kan worden uitgevoerd, enzovoort. Voor het grootste deel heb je gelijk. De meeste programma's draaien op een sequentiële manier. Met dit model kunnen we programma's bouwen die eenvoudig te schrijven en te onderhouden zijn. Er zijn echter specifieke gebruiksgevallen waarin dit sequentiële model niet zou werken of niet optimaal zou zijn. Neem als voorbeeld een boeklezertoepassing. Deze applicatie heeft een aantal geavanceerde functies, zoals het vinden van alle exemplaren van een woord, navigeren tussen bladwijzers en dergelijke. Stel je nu voor dat de gebruiker momenteel een lang boek aan het lezen is en besluit te zoeken naar alle voorkomende woorden, zoals 'De'. Normaal gesproken heeft de toepassing een paar seconden nodig om alle exemplaren van dat woord te vinden en te indexeren. In een sequentieel programma kan de gebruiker geen interactie hebben met de applicatie (de pagina wijzigen of een tekst markeren) totdat de zoekbewerking is voltooid. Hopelijk kun je zien dat dit geen optimale gebruikerservaring is!

1

Het diagram illustreert een typische uitvoeringsstroom van de boeklezertoepassing. Als de gebruiker een langlopende bewerking start (in dit geval het zoeken naar alle exemplaren van “de” in een groot boek), “blokkeert” de toepassing gedurende de gehele duur van die bewerking. In dit geval blijft de gebruiker zonder resultaat op de volgende bladwijzerknop klikken totdat de zoekbewerking is voltooid en worden alle bewerkingen in één keer van kracht, waardoor de eindgebruiker het gevoel krijgt dat de applicatie achterloopt.

Het is je misschien opgevallen dat dit voorbeeld niet echt overeenkomt met het sequentiële model dat we eerder introduceerden. Dit komt omdat de werkzaamheden hier onafhankelijk van elkaar zijn. De gebruiker hoeft niets te weten over het aantal keren dat “de” voorkomt om naar de volgende bladwijzer te kunnen navigeren, dus de volgorde van uitvoering van bewerkingen is niet echt belangrijk. We hoeven niet te wachten op het einde van de zoekactie voordat we naar de volgende bladwijzer kunnen navigeren. Een mogelijke verbetering ten opzichte van de vorige uitvoeringsstroom is gebaseerd op deze logica: we kunnen de lange zoekbewerking op de achtergrond uitvoeren, doorgaan met eventuele binnenkomende bewerkingen, en zodra de lange bewerking is voltooid, kunnen we de gebruiker eenvoudigweg op de hoogte stellen. De uitvoeringsstroom wordt als volgt:

2

Met deze uitvoeringsstroom wordt de gebruikerservaring aanzienlijk verbeterd. Nu kan de gebruiker een langlopende bewerking starten, de applicatie normaal gebruiken en een melding ontvangen zodra de bewerking is voltooid. Dit is de basis van asynchrone programmering.

Javascript ondersteunt onder andere deze stijl van asynchrone programmering door uitgebreide API's te bieden waarmee vrijwel elk asynchroon gedrag kan worden bereikt dat u maar kunt bedenken. Uiteindelijk zou Javascript inherent een asynchrone taal moeten zijn. Als we naar het vorige voorbeeld verwijzen, ligt de asynchrone logica aan de basis van alle toepassingen voor gebruikersinteractie, en Javascript is in de eerste plaats gebouwd voor gebruik in de browser, waar de meeste programma's reageren op gebruikersacties.

Hieronder vindt u een korte handleiding over asynchrone Javascript:

Terugbelgesprekken

In een typisch programma vindt u meestal een aantal functies. Om een ​​functie te gebruiken, roepen we deze aan met een reeks parameters. De functiecode wordt uitgevoerd en retourneert een resultaat, niets bijzonders. Asynchrone programmering verschuift deze logica enigszins. Als we teruggaan naar het voorbeeld van de boeklezertoepassing, kunnen we geen reguliere functie gebruiken om de zoekbewerkingslogica te implementeren, omdat de bewerking een onbekende hoeveelheid tijd in beslag neemt. Een reguliere functie zal in principe terugkeren voordat de bewerking is voltooid, en dit is niet het gedrag dat we verwachten. De oplossing is om een ​​andere functie op te geven die wordt uitgevoerd zodra de zoekbewerking is voltooid. Dit modelleert ons gebruiksscenario, aangezien ons programma zijn werk normaal kan voortzetten en zodra de zoekbewerking is voltooid, wordt de opgegeven functie uitgevoerd om de gebruiker op de hoogte te stellen van de zoekresultaten. Deze functie noemen we een callback-functie:

// Search occurrences function
function searchOccurrences(word, callback) {
  try {
    // search operation logic, result is in result variable
    //....
    callback(null, word, result);
  } catch (err) {
    callback(err);
  }
}

// Search occurrences callback function
function handleSearchOccurrencesResult(err, word, result) {
  if (err) {
    console.log(`Search operation for ${word} ended with an error`);
  } else console.log(`Search results for ${word}: ${result}`);
  return;
}

searchOccurrences("the", handleSearchOccurrencesResult);

Eerst definiëren we de zoekbewerkingsfunctie searchOccurrences. Er is het woord nodig om naar te zoeken en een tweede parameter "callback", die de functie zal zijn die moet worden uitgevoerd zodra de zoekbewerking is voltooid. De zoekfunctie is opzettelijk abstract gehouden, we hoeven ons alleen maar te concentreren op de twee mogelijke uitkomsten: het eerste geval is waarin alles succesvol is verlopen en we het resultaat van de zoekopdracht in de resultaatvariabele hebben. In dit geval hoeven we alleen maar de callback-functie aan te roepen met de volgende parameters: de eerste parameter is nul, wat betekent dat er geen fout is opgetreden, de tweede parameter is het woord waarnaar is gezocht, en de derde en misschien wel belangrijkste parameter van de drie, is het resultaat van de zoekactie.

In het tweede geval treedt er een fout op. Dit is ook een geval waarin de zoekbewerking is uitgevoerd en we de callback-functie moeten aanroepen. We gebruiken een try-and-catch-blok om elke fout te onderscheppen en we roepen gewoon de callback-functie aan met het error-object uit het catch-blok.

Vervolgens hebben we de callback-functie gedefinieerd, handleSearchOccurrences, en hebben we de logica ervan vrij eenvoudig gehouden. Het is gewoon een kwestie van een bericht naar de console afdrukken. We controleren eerst de parameter ‘err’ om te zien of er een fout is opgetreden in de hoofdfunctie. In dat geval laten we de gebruiker gewoon weten dat de zoekactie met een fout is geëindigd. Als er geen fouten zijn opgetreden, printen we een bericht met het resultaat van de zoekactie.

Ten slotte noemen we de functie searchOccurrences met het woord 'de'. De functie zal nu normaal werken zonder het hoofdprogramma te blokkeren en zodra de zoekopdracht is voltooid, wordt de callback uitgevoerd en krijgen we het resultaatbericht met het zoekresultaat of de foutmelding.

Het is belangrijk om hier te vermelden dat we alleen toegang hebben tot de resultaatvariabele binnen de hoofd- en callback-functies. Als we zoiets als dit proberen:

let result;
function searchOccurrences(word, callback) {
  try {
    // search operation logic, result is in searchResult variable
    //....
    result = searchResult;
    callback(null, word, result);
  } catch (err) {
    callback(err);
  }
}
searchOccurrences("the", handleSearchOccurrencesResult);
console.log(result);

het resultaat van de afdruk zou ongedefinieerd zijn omdat de programma's niet wachten tot de functie searchOccurrences wordt uitgevoerd. Het gaat naar de volgende instructie, de printinstructie, voordat de resultaatvariabele binnen de hoofdfunctie wordt toegewezen. Als gevolg hiervan zullen we de niet-toegewezen resultaatvariabele afdrukken.

Dus op basis van deze logica moeten we alle code die de resultaatvariabele gebruikt binnen de callback-functie bewaren. Dit lijkt op dit moment misschien geen probleem, maar het kan snel uitgroeien tot een echt probleem. Stel je het geval voor waarin we een keten van asynchrone functies hebben die achter elkaar moeten worden uitgevoerd. In de typische callback-logica zou je zoiets als dit implementeren:

functionA(function (err, resA) {
  ///......
  functionB(resA, function (err, resB) {
    ///......
    functionC(resB, function (err, resC) {
      ///......
      functionD(resC, function (err, resD) {
        ///......
      });
    });
  });
});

Houd er rekening mee dat elke callback een foutparameter heeft en dat elke fout afzonderlijk moet worden afgehandeld. Dit maakt de toch al complexe code hierboven nog complexer en moeilijker te onderhouden. Hopelijk zijn Promises hier om het callback hell-probleem op te lossen, dat zullen we hierna bespreken.

Beloften

Beloftes zijn gebaseerd op terugbelverzoeken en werken op een vergelijkbare manier. Ze werden geïntroduceerd als onderdeel van ES6-functies om een ​​paar van de opvallende problemen met callbacks, zoals callback hell, op te lossen. Beloftes bieden hun eigen functies die worden uitgevoerd bij succesvolle voltooiing (oplossen) en wanneer er fouten optreden (afwijzen). Hieronder ziet u het voorbeeld van searchOccurrences, geïmplementeerd met beloften:

// Search occurrences function
function searchOccurrences(word) {
  return new Promise((resolve, reject) => {
    try {
      // search operation logic, result is in result variable
      //....
      resolve(word, result);
    } catch (err) {
      reject(err);
    }
  });
}

searchOccurrences("the")
  .then((word, result) => {
    console.log(`Search results for ${word}: ${result}`);
  })
  .catch((err) => {
    console.log(`Search operation ended with an error`);
  });

Laten we de wijzigingen bekijken die we hebben toegepast:

De functie searchOccurrences retourneert een belofte. Binnen de belofte houden we dezelfde logica aan: we hebben twee functies oplossen en afwijzen die onze callbacks vertegenwoordigen, in plaats van een enkele callback-functie te hebben die zowel een succesvolle uitvoering als een uitvoering met fouten afhandelt. Beloften scheiden de twee uitkomsten en zorgen voor een schone syntaxis bij het aanroepen van de hoofdfunctie. De oplossingsfunctie is 'gekoppeld' aan de hoofdfunctie met behulp van het trefwoord 'dan'. Hier specificeren we alleen de twee parameters van de oplossingsfunctie en drukken we het zoekresultaat af. Iets soortgelijks geldt voor de afwijsfunctie; deze kan worden gekoppeld met het trefwoord 'catch'. Hopelijk kunt u de voordelen waarderen die beloften bieden op het gebied van leesbaarheid en netheid van de code. Als je er nog steeds over aan het debatteren bent, kijk dan eens hoe we het callback hell-probleem kunnen oplossen door de asynchrone functies aan elkaar te koppelen en de een na de ander uit te voeren:

searchOccurrences("the")
  .then(searchOccurrences("asynchronous"))
  .then(searchOccurrences("javascript"))
  .then(searchOccurrences("guide"))
  .catch((err) => {
    console.log(`Search operation ended with an error`);
  });

Asynchroon/Afwachten

Async/Await is de nieuwste toevoeging aan onze asynchrone toolbelt in Javascript. Geïntroduceerd met ES8, bieden ze een nieuwe abstractielaag bovenop asynchrone functies door simpelweg te “wachten” op de uitvoering van een asynchrone bewerking. De stroom van het programma blokkeert bij die instructie totdat er een resultaat wordt geretourneerd van de asynchrone bewerking, waarna het programma doorgaat met de volgende instructie. Als u denkt aan de synchrone uitvoeringsstroom, heeft u gelijk. De cirkel is rond! Async/await probeert de eenvoud van synchrone programmering naar de asynchrone wereld te brengen. Houd er rekening mee dat dit alleen wordt waargenomen in de uitvoering en de code van het programma. Alles blijft hetzelfde onder de motorkap, Async/await gebruikt nog steeds beloftes en callbacks zijn hun bouwstenen.

Laten we ons voorbeeld bekijken en implementeren met Async/await:

async function searchOccurrences(word) {
  try {
    // search operation logic, result is in result variable
    //....
    return result;
  } catch (err) {
    console.log(`Search operation for ${word} ended with an error`);
  }
}

const word = "the";

const result = await searchOccurrences(word, handleSearchOccurrencesResult);

console.log(`Search results for ${word}: ${result}`);

Onze code is niet veel veranderd. Het belangrijkste om hier op te merken is het trefwoord "async" vóór de functiedeclaratie searchOccurrences. Dit geeft aan dat de functie asynchroon is. Let ook op het trefwoord “await” bij het aanroepen van de functie searchOccurrences. Dit zal het programma instrueren om te wachten op de uitvoering van de functie totdat het resultaat wordt geretourneerd voordat het programma naar de volgende instructie kan gaan. Met andere woorden: de resultaatvariabele zal altijd de geretourneerde waarde van de searchOccurrences-functie bevatten en niet de belofte van de functie, in die zin heeft Async/Await geen status in behandeling als Promises. Zodra de uitvoering is voltooid, gaan we naar de print-instructie en deze keer bevat het resultaat feitelijk het resultaat van de zoekbewerking. Zoals verwacht gedraagt ​​de nieuwe code zich alsof deze synchroon is.

Een ander klein ding om in gedachten te houden is dat, aangezien we niet langer callback-functies hebben, we de searchOccurrences-fout binnen dezelfde functie moeten afhandelen, omdat we de fout niet zomaar kunnen doorgeven aan de callback-functie en deze daar kunnen afhandelen. Ter wille van het voorbeeld drukken we hier slechts een foutmelding af in geval van een fout.

Afronding

In dit artikel hebben we de verschillende benaderingen besproken die worden gebruikt om asynchrone logica in Javascript te implementeren. We zijn begonnen met het verkennen van een concreet voorbeeld van waarom we zouden moeten overstappen van de reguliere synchrone programmeerstijl naar het asynchrone model. Vervolgens zijn we overgegaan op callbacks, de belangrijkste bouwstenen van asynchrone Javascript. De beperkingen van callbacks leidden ons naar de verschillende alternatieven die in de loop der jaren zijn toegevoegd om deze beperkingen te overwinnen, voornamelijk beloftes en Async/await. Asynchrone logica is overal op internet te vinden, of u nu een externe API aanroept, een databasequery initieert, naar het lokale bestandssysteem schrijft of zelfs wacht op gebruikersinvoer op een inlogformulier. Hopelijk heb je nu meer zelfvertrouwen om deze problemen aan te pakken door schoon en onderhoudbaar asynchrone Javascript te schrijven!

Als je dit artikel leuk vindt, bekijk dan de CLA Blog waar we verschillende onderwerpen bespreken over hoe je in de technologie terecht kunt komen. Bekijk ook ons ​​youtube-kanaal voor onze eerdere gratis workshops en volg ons op social media zodat je de aankomende niet mist!


Maak je carrière toekomstbestendig door je vaardigheden bij te scholen in HTML, CSS en JavaScript met de [Web Development Bootcamp] van Code Labs Academy(/courses/web-development).


Career Services background pattern

Carrièrediensten

Contact Section background image

Laten we in contact blijven

Code Labs Academy © 2024 Alle rechten voorbehouden.