Mester Asynkron JavaScript: Løfter, Async/Await & Tilbakekallinger

Forståelse av Asynkrone Funksjoner i JavaScript

Når du utvikler JavaScript-applikasjoner, vil du sannsynligvis støte på asynkrone funksjoner, som for eksempel fetch-funksjonen i nettlesere eller readFile-funksjonen i Node.js.

Hvis du har brukt disse funksjonene på samme måte som synkrone funksjoner, kan du ha opplevd uventede resultater. Dette skyldes at de er asynkrone. Denne artikkelen vil gi deg en grundig forståelse av hva asynkrone funksjoner er, og hvordan du kan bruke dem effektivt.

Introduksjon til Synkrone Funksjoner

JavaScript er et single-threaded språk, noe som betyr at det kun kan utføre én operasjon om gangen. Hvis prosessoren støter på en funksjon som tar lang tid å fullføre, vil JavaScript vente til denne funksjonen er ferdig før den fortsetter med resten av programmet.

De fleste funksjoner utføres direkte av prosessoren. Dette betyr at under utførelsen av disse funksjonene, uansett hvor lang tid det tar, vil prosessoren være fullt opptatt. Disse funksjonene kalles synkrone. Et eksempel på en synkron funksjon er definert nedenfor:

    function add(a, b) {
        for (let i = 0; i < 1000000; i ++) {
            // Gjør ingenting
        }
        return a + b;
    }

    // Å kalle funksjonen vil ta litt tid
    sum = add(10, 5);

    // Prosessoren kan ikke gå videre til neste linje før add-funksjonen er ferdig
    console.log(sum);
  

Denne funksjonen utfører en stor løkke som tar lang tid før den returnerer summen av de to argumentene.

Etter å ha definert funksjonen, kalte vi den og lagret resultatet i variabelen sum. Deretter logget vi verdien av sum. Selv om add-funksjonen tar litt tid å utføre, kan prosessoren ikke fortsette med å logge summen før hele funksjonen er ferdig.

De aller fleste funksjoner du kommer over vil oppføre seg på en forutsigbar måte som den ovenfor. Imidlertid er noen funksjoner asynkrone og oppfører seg ikke som vanlige funksjoner.

Introduksjon til Asynkrone Funksjoner

Asynkrone funksjoner utfører mesteparten av sitt arbeid utenfor prosessoren. Dette betyr at selv om funksjonen kan ta tid å fullføre, vil prosessoren være ledig og tilgjengelig for å utføre andre oppgaver.

Her er et eksempel på en asynkron funksjon:

      fetch('https://jsonplaceholder.typicode.com/users/1');
  

For å forbedre effektiviteten, lar JavaScript prosessoren gå videre til andre oppgaver som krever CPU, selv før den asynkrone funksjonen er ferdig.

Siden prosessoren fortsatte før den asynkrone funksjonen var ferdig, vil ikke resultatet være umiddelbart tilgjengelig. Det vil ta litt tid før det er klart. Hvis prosessoren prøvde å kjøre deler av programmet som var avhengig av dette resultatet, ville det oppstå feil.

Derfor bør prosessoren kun kjøre deler av programmet som ikke er avhengig av resultatet fra den asynkrone funksjonen. For å håndtere dette, bruker moderne JavaScript promises (løfter).

Hva er et Promise i JavaScript?

I JavaScript er et promise en midlertidig verdi som returneres av en asynkron funksjon. Promises er ryggraden i moderne asynkron programmering i JavaScript.

Når et promise er opprettet, kan to ting skje. Enten blir det «resolved» (løst) når den asynkrone funksjonens returverdi er klar, eller så blir det «rejected» (avvist) i tilfelle det oppstår en feil. Dette er hendelsene i et promises livssyklus. Vi kan knytte hendelsesbehandlere til et promise, som kalles når promise enten løses eller avvises.

All kode som krever den endelige verdien fra en asynkron funksjon, kan knyttes til promises hendelsesbehandler for når den løses. All kode som håndterer feilen ved et avvist promise, vil også knyttes til den tilhørende hendelsesbehandleren.

Her er et eksempel der vi leser data fra en fil i Node.js:

      const fs = require('fs/promises');

      fileReadPromise = fs.readFile('./hello.txt', 'utf-8');

      fileReadPromise.then((data) => console.log(data));

      fileReadPromise.catch((error) => console.log(error));
  

I den første linjen importerer vi fs/promises-modulen.

I den andre linjen kaller vi readFile-funksjonen og sender inn filnavnet og kodingen. Denne funksjonen er asynkron, og returnerer derfor et promise. Vi lagrer dette promise i variabelen fileReadPromise.

I den tredje linjen har vi knyttet en hendelseslytter for når promise løses. Vi gjør dette ved å kalle then-metoden på promise-objektet. Som argument til then-metoden gir vi en funksjon som skal kjøres når promise løses.

I den fjerde linjen knytter vi en lytter for når promise avvises. Dette gjøres ved å kalle catch-metoden og sende inn feilhåndtereren som argument.

En alternativ tilnærming er å bruke async og await-nøkkelord. Vi vil gå gjennom denne tilnærmingen neste gang.

Forklaring av Async og Await

Async og await-nøkkelordene kan brukes til å skrive asynkront JavaScript på en mer lesbar måte. I denne seksjonen vil jeg forklare hvordan disse nøkkelordene fungerer og hvilken effekt de har på koden din.

Nøkkelordet await brukes til å midlertidig stoppe utførelsen av en funksjon mens man venter på at en asynkron funksjon skal fullføres. Her er et eksempel:

      const fs = require('fs/promises');

      async function readData() {
      	const data = await fs.readFile('./hello.txt', 'utf-8');

          // Denne linjen vil ikke kjøre før data er tilgjengelig
      	console.log(data);
      }

      readData()
  

Vi brukte nøkkelordet await når vi kalte readFile. Dette instruerte prosessoren om å vente til filen er lest før neste linje (console.log) kan kjøres. Dette bidrar til å sikre at kode som er avhengig av resultatet fra en asynkron funksjon ikke utføres før resultatet er tilgjengelig.

Hvis du prøvde å kjøre koden ovenfor, ville du fått en feil. Dette er fordi await kun kan brukes i en asynkron funksjon. For å deklarere en funksjon som asynkron, bruker du nøkkelordet async før funksjonsdeklarasjonen, slik:

      const fs = require('fs/promises');

      async function readData() {
      	const data = await fs.readFile('./hello.txt', 'utf-8');

          // Denne linjen vil ikke kjøre før data er tilgjengelig
      	console.log(data);
      }

      // Kaller funksjonen slik at den kjører
      readData()

      // Kode på dette punktet vil kjøre mens vi venter på at readData-funksjonen skal fullføres
      console.log('Venter på at data skal bli tilgjengelig')
  

Når du kjører denne kodesnutten, vil du se at JavaScript kjører den ytre console.log mens den venter på at dataene som leses fra tekstfilen blir tilgjengelige. Når dataene er tilgjengelige, kjøres console.log inne i readData.

Feilhåndtering ved bruk av async og await gjøres vanligvis ved hjelp av try/catch-blokker. Det er også viktig å vite hvordan du kan loope med asynkron kode.

Async og await er tilgjengelig i moderne JavaScript. Tidligere ble asynkron kode skrevet ved hjelp av callbacks.

Introduksjon til Tilbakeringinger (Callbacks)

En callback er en funksjon som kalles når et resultat er tilgjengelig. All kode som krever returverdien, legges i callback-funksjonen. All kode utenfor callbacken er ikke avhengig av resultatet og kan utføres fritt.

Her er et eksempel som leser en fil i Node.js:

      const fs = require("fs");

      fs.readFile("./hello.txt", "utf-8", (err, data) => {

      	// I denne callbacken legger vi all kode som krever resultatet
      	if (err) console.log(err);
      	else console.log(data);
      });

      // Her kan vi utføre oppgaver som ikke krever resultatet
      console.log("Hallo fra programmet")
  

I den første linjen importerte vi fs-modulen. Deretter kalte vi readFile-funksjonen til fs-modulen. readFile-funksjonen leser tekst fra en fil vi spesifiserer. Det første argumentet er filen, og det andre spesifiserer filformatet.

readFile-funksjonen leser teksten fra filen asynkront. For å gjøre dette tar den inn en funksjon som et argument. Dette funksjonsargumentet er en callback-funksjon, og den vil bli kalt når dataene er lest.

Det første argumentet som sendes når callback-funksjonen kalles, er en feil, som vil ha en verdi hvis en feil oppstår under utførelsen av funksjonen. Hvis det ikke oppstår noen feil, vil den være undefined.

Det andre argumentet som sendes til callback-funksjonen, er dataene som er lest fra filen. Koden inne i denne funksjonen vil få tilgang til dataene fra filen. Kode utenfor denne funksjonen krever ikke data fra filen, og kan derfor kjøres mens vi venter på data fra filen.

Å kjøre koden ovenfor vil gi følgende resultat:

Viktige JavaScript-Funksjoner

Det er noen nøkkelfunksjoner og egenskaper som påvirker hvordan asynkront JavaScript fungerer. Disse er godt forklart i videoen nedenfor:

Jeg har kort oppsummert de to viktigste funksjonene nedenfor.

#1. Single-Threaded (Enkeltrådet)

I motsetning til andre språk som lar programmereren bruke flere tråder, lar JavaScript deg bare bruke én tråd. En tråd er en sekvens av instruksjoner som logisk avhenger av hverandre. Flere tråder lar programmet kjøre en annen tråd når det oppstår blokkerende operasjoner.

Imidlertid øker flere tråder kompleksiteten og gjør det vanskeligere å forstå programmene som bruker dem. Dette øker sannsynligheten for at det vil bli introdusert feil i koden, og det blir vanskeligere å feilsøke koden. JavaScript ble laget som en single-threaded for enkelhets skyld. Som et single-threaded språk er det avhengig av å være hendelsesdrevet for å håndtere blokkerende operasjoner effektivt.

#2. Event-Driven (Hendelsesdrevet)

JavaScript er også hendelsesdrevet. Dette betyr at visse hendelser skjer i løpet av et JavaScript-programs livssyklus. Som programmerer kan du knytte funksjoner til disse hendelsene, og hver gang hendelsen skjer, vil den tilknyttede funksjonen bli kalt og utført.

Noen hendelser kan skyldes at resultatet av en blokkerende operasjon er tilgjengelig. I dette tilfellet kalles den tilhørende funksjonen med resultatet.

Ting du bør vurdere når du skriver Asynkront JavaScript

I denne siste delen vil jeg nevne noen ting du bør vurdere når du skriver asynkront JavaScript. Dette inkluderer nettleserstøtte, beste praksis og viktigheten av dette.

Nettleserstøtte

Dette er en tabell som viser støtten for promises i forskjellige nettlesere.

Kilde: caniuse.com

Dette er en tabell som viser støtte for asynkrone nøkkelord i forskjellige nettlesere.

Kilde: caniuse.com

Beste Praksis

  • Velg alltid async/await da det hjelper deg med å skrive renere kode som er enklere å forstå.
  • Håndter feil i try/catch-blokker.
  • Bruk nøkkelordet async kun når det er nødvendig å vente på resultatet fra en funksjon.

Viktigheten av Asynkron Kode

Asynkron kode lar deg skrive mer effektive programmer som bare bruker én tråd. Dette er viktig siden JavaScript brukes til å bygge nettsider som utfører mange asynkrone operasjoner, som for eksempel nettverksforespørsler og lesing/skriving av filer til disk. Denne effektiviteten har gjort det mulig for runtime-miljøer som NodeJS å vokse i popularitet som det foretrukne runtime-miljøet for applikasjonsservere.

Avsluttende Ord

Dette har vært en lang artikkel, men vi har dekket hvordan asynkrone funksjoner skiller seg fra vanlige synkrone funksjoner. Vi har også dekket hvordan du bruker asynkron kode ved å bruke promises, async/await og callbacks.

I tillegg har vi dekket nøkkelfunksjoner i JavaScript. Til slutt har vi dekket nettleserstøtte og beste praksis.

Deretter kan du sjekke ut vanlige intervjuspørsmål om Node.js.