En begyndervejledning til asynkron JavaScript

javascript
promises
AsyncAwait
En begyndervejledning til asynkron JavaScript cover image

Hvis du kun er i gang med programmering, tænker du sandsynligvis på programmer som et sæt af sekventielle logiske blokke, hvor hver blok gør en bestemt ting og sender sit resultat videre, så den næste blok kan køre og så videre, og for for det meste har du ret, de fleste programmer kører på en sekventiel måde, denne model giver os mulighed for at bygge programmer, der er enkle at skrive og vedligeholde. Der er dog specifikke brugstilfælde, hvor denne sekventielle model ikke ville fungere eller ikke ville være optimal. Som et eksempel kan du overveje en boglæserapplikation. Denne applikation har et par avancerede funktioner, såsom at finde alle forekomster af et ord, navigere mellem bogmærker og lignende. Forestil dig nu, at brugeren i øjeblikket læser en lang bog og beslutter sig for at lede efter alle forekomster af et almindeligt ord såsom "The". Applikationen vil normalt tage et par sekunder at finde og indeksere alle forekomster af det ord. I et sekventielt program kan brugeren ikke interagere med applikationen (ændre siden eller fremhæve en tekst), før søgeoperationen er opfyldt. Forhåbentlig kan du se, at det ikke er en optimal brugeroplevelse!

1

Diagrammet illustrerer et typisk udførelsesflow for boglæserapplikationen. Hvis brugeren starter en langvarig operation (i dette tilfælde søgningen efter alle forekomster af "den" i en stor bog), "fryser" applikationen i hele varigheden af ​​denne operation. I dette tilfælde vil brugeren blive ved med at klikke på den næste bogmærke-knap uden resultat, indtil søgeoperationen er afsluttet, og alle handlingerne vil træde i kraft på én gang, hvilket giver slutbrugeren følelsen af ​​en haltende applikation.

Du har måske bemærket, at dette eksempel ikke rigtig svarer til den sekventielle model, vi introducerede tidligere. Det skyldes, at operationerne her er uafhængige af hinanden. Brugeren behøver ikke at vide om antallet af forekomster af "den" for at navigere til det næste bogmærke, så rækkefølgen for udførelse af operationer er ikke rigtig vigtig. Vi behøver ikke vente på slutningen af ​​søgeoperationen, før vi kan navigere til det næste bogmærke. En mulig forbedring af det tidligere udførelsesflow er baseret på denne logik: vi kan køre den lange søgeoperation i baggrunden, fortsætte med alle indkommende operationer, og når den lange operation er udført, kan vi blot give brugeren besked. Udførelsesflowet bliver som følger:

2

Med dette udførelsesflow er brugeroplevelsen væsentligt forbedret. Nu kan brugeren starte en langvarig operation, fortsætte med at bruge applikationen normalt og få besked, når handlingen er udført. Dette er grundlaget for asynkron programmering.

Javascript, blandt andre sprog, understøtter denne stil af asynkron programmering ved at levere omfattende API'er for at opnå næsten enhver asynkron adfærd, du kan tænke på. I slutningen af ​​dagen skulle Javascript i sagens natur være et asynkront sprog. Hvis vi refererer til det foregående eksempel, er den asynkrone logik i bunden af ​​alle brugerinteraktionsapplikationer, og Javascript blev primært bygget til at blive brugt på browseren, hvor de fleste af programmerne handler om at reagere på brugerhandlinger.

Følgende vil give dig en kort guide om Asynkron Javascript:

Tilbagekald

I et typisk program vil du normalt finde en række funktioner. For at bruge en funktion kalder vi den med et sæt parametre. Funktionskoden vil udføre og returnere et resultat, intet ud over det sædvanlige. Asynkron programmering flytter denne logik lidt. Går vi tilbage til eksemplet med boglæserapplikationen, kan vi ikke bruge en almindelig funktion til at implementere søgeoperationslogikken, da operationen tager en ukendt tid. En almindelig funktion vil som udgangspunkt vende tilbage, før operationen er udført, og det er ikke den adfærd, vi forventer. Løsningen er at specificere en anden funktion, der vil blive udført, når søgeoperationen er udført. Dette modellerer vores use case, da vores program kan fortsætte sit flow normalt, og når søgeoperationen er afsluttet, vil den angivne funktion udføres for at underrette brugeren om søgeresultaterne. Denne funktion kalder vi en tilbagekaldsfunktion:

// 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);

Først definerer vi søgeoperationsfunktionen, searchOccurrences. Det kræver ordet at søge efter og en anden parameter "tilbagekald", som vil være den funktion, der skal udføres, når søgeoperationen er udført. Søgeoperationsfunktionen blev med vilje holdt abstrakt, vi behøver kun at fokusere på dens to mulige udfald: det første tilfælde er, hvor alt gik vellykket, og vi har resultatet af søgningen i resultatvariablen. I dette tilfælde skal vi blot kalde tilbagekaldsfunktionen med følgende parametre: den første parameter er null, hvilket betyder, at der ikke er opstået en fejl, den anden parameter er det ord, der blev søgt efter, og den tredje og måske vigtigste parameter af de tre, er resultatet af søgeoperationen.

Det andet tilfælde er, hvor der opstår en fejl, dette er også et tilfælde, hvor udførelsen af ​​søgeoperationen er udført, og vi er nødt til at kalde tilbagekaldsfunktionen. Vi bruger en try and catch-blok til at opsnappe enhver fejl, og vi kalder bare tilbagekaldsfunktionen med fejlobjektet fra catch-blokken.

Vi definerede derefter tilbagekaldsfunktionen, handleSearchOccurrences, vi holdt dens logik ret simpel. Det er bare et spørgsmål om at udskrive en besked til konsollen. Vi kontrollerer først parameteren "err" for at se, om der opstod en fejl i hovedfunktionen. I så fald lader vi blot brugeren vide, at søgeoperationen endte med en fejl. Hvis der ikke blev rejst fejl, udskriver vi en meddelelse med resultatet af søgeoperationen.

Til sidst kalder vi søgningenOccurrences-funktionen med ordet "the". Funktionen vil nu køre normalt uden at blokere hovedprogrammet, og når søgningen er udført, vil tilbagekaldet blive udført, og vi får resultatmeddelelsen enten med søgeresultatet eller fejlmeddelelsen.

Det er vigtigt at nævne her, at vi kun har adgang til resultatvariablen inde i hoved- og tilbagekaldsfunktionerne. Hvis vi prøver noget som dette:

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);

resultatet af udskriften ville være udefineret, fordi programmerne ikke venter på, at funktionen searchOccurrences udføres. Den flytter til den næste instruktion, som er print-sætningen, før resultatvariablen tildeles inde i hovedfunktionen. Som et resultat vil vi udskrive den ikke-tildelte resultatvariabel.

Så baseret på denne logik bør vi beholde al den kode, der bruger resultatvariablen, inde i tilbagekaldsfunktionen. Dette ser måske ikke ud til at være et problem nu, men det kan hurtigt eskalere til et reelt problem. Forestil dig det tilfælde, hvor vi har en kæde af asynkrone funktioner, der skal køre i rækkefølge. I den typiske tilbagekaldslogik vil du implementere noget som dette:

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

Husk, at hvert tilbagekald har en fejlparameter, og hver fejl skal håndteres separat. Dette gør den allerede komplekse kode ovenfor endnu mere kompleks og svær at vedligeholde. Forhåbentlig er Promises her for at løse problemet med tilbagekaldshelvede, det vil vi dække næste gang.

Løfter

Løfter er bygget oven på tilbagekald og fungerer på samme måde. De blev introduceret som en del af ES6-funktioner for at løse et par af de grelle problemer med tilbagekald, såsom tilbagekaldshelvede. Løfter giver deres egne funktioner, der kører ved succesfuld gennemførelse (løsning), og når der opstår fejl (afvis). Følgende viser searchOccurrences-eksemplet implementeret med løfter:

// 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`);
  });

Lad os gennemgå de ændringer, vi har anvendt:

SearchOccurrences-funktionen returnerer et løfte. Inde i løftet holder vi den samme logik: Vi har to funktioner løse og afvise, der repræsenterer vores tilbagekald frem for at have en enkelt tilbagekaldsfunktion, der håndterer både en vellykket eksekvering og en udførelse med fejl. Løfter adskiller de to resultater og giver en ren syntaks, når hovedfunktionen kaldes. Løsningsfunktionen er "hooked" til hovedfunktionen ved hjælp af nøgleordet "then". Her angiver vi blot de to parametre for løsningsfunktionen og udskriver søgeresultatet. En lignende ting gælder for afvisningsfunktionen, den kan tilsluttes ved hjælp af nøgleordet "fangst". Forhåbentlig kan du værdsætte de fordele, løfter tilbyder med hensyn til kodelæsbarhed og renlighed. Hvis du stadig diskuterer det, så tjek hvordan vi kan løse problemet med tilbagekaldshelvede ved at kæde de asynkrone funktioner sammen for at køre den ene efter den anden:

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

Asynkron/afvent

Async/Await er den seneste tilføjelse til vores asynkrone værktøjsbælte i Javascript. Introduceret med ES8 giver de et nyt lag af abstraktion oven på asynkrone funktioner ved blot at "vente" på udførelsen af ​​en asynkron operation. Flowet af programmet blokerer ved den instruktion, indtil et resultat returneres fra den asynkrone operation, og derefter vil programmet fortsætte med den næste instruktion. Hvis du tænker på det synkrone udførelsesflow, har du ret. Vi er kommet fuld cirkel! Async/wait forsøg på at bringe enkelheden ved synkron programmering til den asynkrone verden. Vær opmærksom på, at dette kun opfattes i programmets udførelse og kode. Alt forbliver det samme under motorhjelmen, Async/await bruger stadig løfter, og tilbagekald er deres byggesten.

Lad os gennemgå vores eksempel og implementere det ved hjælp af 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}`);

Vores kode ændrede sig ikke meget, det vigtige at lægge mærke til her er nøgleordet "async" før funktionsdeklarationen searchOccurrences. Dette indikerer, at funktionen er asynkron. Læg også mærke til nøgleordet "afvent", når du kalder funktionen SearchOccurrences. Dette vil instruere programmet til at vente på udførelsen af ​​funktionen, indtil resultatet returneres, før programmet kan flytte til den næste instruktion, med andre ord, resultatvariablen vil altid indeholde den returnerede værdi af funktionen searchOccurrences og ikke løftet om funktionen, i den forstand har Async/Await ikke en ventende tilstand som løfter. Når udførelsen er færdig, går vi til print-sætningen, og denne gang indeholder resultatet faktisk resultatet af søgeoperationen. Som forventet har den nye kode den samme adfærd, som hvis den var synkron.

En anden mindre ting at huske på er, at da vi ikke længere har tilbagekaldsfunktioner, skal vi håndtere searchOccurrences-fejlen inde i den samme funktion, da vi ikke bare kan udbrede fejlen til tilbagekaldsfunktionen og håndtere den der. Her udskriver vi blot en fejlmeddelelse i tilfælde af fejl for eksemplets skyld.

Afslutning

I denne artikel gennemgik vi de forskellige tilgange, der bruges til at implementere asynkron logik i Javascript. Vi startede med at udforske et konkret eksempel på, hvorfor vi skulle skifte fra den almindelige synkrone stil med programmering til den asynkrone model. Vi gik derefter over til tilbagekald, som er de vigtigste byggesten i asynkron Javascript. Begrænsningerne af tilbagekald førte os til de forskellige alternativer, der blev tilføjet gennem årene for at overvinde disse begrænsninger, primært løfter og Async/await. Asynkron logik kan findes overalt på nettet, uanset om du kalder en ekstern API, starter en databaseforespørgsel, skriver til det lokale filsystem eller venter på brugerinput på en login-formular. Forhåbentlig føler du dig nu mere sikker på at tackle disse problemer ved at skrive rent og vedligeholdeligt asynkront Javascript!

Hvis du kan lide denne artikel, så tjek venligst CLA-bloggen, hvor vi diskuterer forskellige emner om, hvordan du kommer ind i teknologien. Tjek også vores youtube-kanal for vores tidligere gratis workshops og følg os på sociale medier, så du ikke går glip af kommende!


Career Services background pattern

Karriereservice

Contact Section background image

Lad os holde kontakten

Code Labs Academy © 2024 Alle rettigheder forbeholdes.