Vodnik za začetnike po asinhronem JavaScriptu

javascript
promises
AsyncAwait
Vodnik za začetnike po asinhronem JavaScriptu cover image

Če ste šele začeli s programiranjem, obstaja velika verjetnost, da o programih razmišljate kot o nizu zaporednih blokov logike, kjer vsak blok naredi določeno stvar in posreduje svoj rezultat, tako da se lahko naslednji blok izvaja in tako naprej, in za večinoma imate prav, večina programov teče zaporedno, ta model nam omogoča izdelavo programov, ki jih je preprosto napisati in vzdrževati. Obstajajo pa posebni primeri uporabe, ko ta zaporedni model ne bi deloval ali ne bi bil optimalen. Kot primer razmislite o aplikaciji za branje knjig. Ta aplikacija ima nekaj naprednih funkcij, kot je iskanje vseh pojavitev besede, navigacija med zaznamki in podobno. Zdaj si predstavljajte, da uporabnik trenutno bere dolgo knjigo in se odloči poiskati vse pojavitve pogoste besede, kot je "The". Aplikacija običajno potrebuje nekaj sekund, da poišče in indeksira vse pojavitve te besede. V zaporednem programu uporabnik ne more komunicirati z aplikacijo (spremeniti strani ali označiti besedila), dokler ni končana iskalna operacija. Upajmo, da vidite, da to ni optimalna uporabniška izkušnja!

1

Diagram ponazarja tipičen tok izvajanja aplikacije za branje knjig. Če uporabnik sproži dolgotrajno operacijo (v tem primeru iskanje vseh pojavitev »the« v veliki knjigi), aplikacija »zamrzne« za ves čas te operacije. V tem primeru bo uporabnik še naprej klikal gumb za naslednji zaznamek brez rezultatov, dokler operacija iskanja ni končana in vse operacije bodo začele veljati hkrati, kar bo dalo končnemu uporabniku občutek zaostajajoče aplikacije.

Morda ste opazili, da ta primer v resnici ne ustreza zaporednemu modelu, ki smo ga predstavili prej. To je zato, ker so operacije tukaj neodvisne ena od druge. Uporabniku ni treba vedeti o številu pojavitev »the«, da bi se pomaknil do naslednjega zaznamka, zato vrstni red izvajanja operacij v resnici ni pomemben. Ni nam treba čakati na konec iskanja, preden se lahko pomaknemo do naslednjega zaznamka. Možna izboljšava prejšnjega toka izvajanja temelji na tej logiki: operacijo dolgega iskanja lahko zaženemo v ozadju, nadaljujemo z morebitnimi dohodnimi operacijami in ko je dolga operacija končana, lahko preprosto obvestimo uporabnika. Potek izvajanja postane naslednji:

2

S tem potekom izvajanja je uporabniška izkušnja znatno izboljšana. Zdaj lahko uporabnik sproži dolgotrajno operacijo, nadaljuje z normalno uporabo aplikacije in prejme obvestilo, ko je operacija končana. To je osnova asinhronega programiranja.

Javascript med drugimi jeziki podpira ta stil asinhronega programiranja z zagotavljanjem obsežnih API-jev za doseganje skoraj katerega koli asinhronega vedenja, ki si ga lahko zamislite. Konec koncev bi moral biti Javascript sam po sebi asinhroni jezik. Če se sklicujemo na prejšnji primer, je asinhrona logika osnova vseh aplikacij za uporabniško interakcijo, Javascript pa je bil v prvi vrsti zgrajen za uporabo v brskalniku, kjer se večina programov odziva na dejanja uporabnikov.

V nadaljevanju boste našli kratek vodnik o asinhronem Javascriptu:

Povratni klici

V običajnem programu boste običajno našli številne funkcije. Za uporabo funkcije jo pokličemo z nizom parametrov. Funkcijska koda se bo izvedla in vrnila rezultat, nič nenavadnega. Asinhrono programiranje to logiko nekoliko premakne. Če se vrnemo k primeru aplikacije za branje knjig, ne moremo uporabiti navadne funkcije za implementacijo logike iskalne operacije, ker operacija traja neznano količino časa. Redna funkcija se bo v bistvu vrnila, preden bo operacija opravljena, in to ni vedenje, ki ga pričakujemo. Rešitev je, da določite drugo funkcijo, ki bo izvedena, ko bo iskalna operacija končana. To modelira naš primer uporabe, saj lahko naš program normalno nadaljuje svoj tok in ko je iskalna operacija končana, se izvede določena funkcija, ki uporabnika obvesti o rezultatih iskanja. To funkcijo imenujemo funkcija povratnega klica:

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

Najprej definiramo funkcijo iskalne operacije, searchOccurrences. Potrebuje besedo za iskanje in drugi parameter "povratni klic", ki bo funkcija, ki se bo izvršila, ko bo iskalna operacija končana. Funkcija iskalne operacije je bila namenoma abstraktna, osredotočiti se moramo le na njena dva možna izida: v prvem primeru je šlo vse uspešno in imamo rezultat iskanja v spremenljivki rezultata. V tem primeru moramo samo poklicati funkcijo povratnega klica z naslednjimi parametri: prvi parameter je null, kar pomeni, da ni prišlo do napake, drugi parameter je beseda, ki je bila iskana, tretji in morda najpomembnejši parameter od treh, je rezultat iskalne akcije.

Drugi primer je, ko pride do napake, to je tudi primer, ko je izvedena iskalna operacija in moramo poklicati funkcijo povratnega klica. Za prestrezanje morebitne napake uporabljamo blok poskusi in ulovi in ​​samo pokličemo funkcijo povratnega klica z objektom napake iz bloka ulova.

Nato smo definirali funkcijo povratnega klica, handleSearchOccurrences, njeno logiko smo ohranili povsem preprosto. Gre le za tiskanje sporočila na konzolo. Najprej preverimo parameter »err«, da vidimo, ali je prišlo do napake v glavni funkciji. V tem primeru uporabnika samo obvestimo, da se je iskalna operacija končala z napako. Če ni prišlo do nobene napake, natisnemo sporočilo z rezultatom iskalne operacije.

Končno pokličemo funkcijo searchOccurrences z besedo "the". Funkcija bo zdaj delovala normalno brez blokiranja glavnega programa in ko bo iskanje končano, bo izveden povratni klic in prejeli bomo sporočilo o rezultatu z rezultatom iskanja ali sporočilom o napaki.

Tukaj je pomembno omeniti, da imamo dostop samo do spremenljivke rezultata znotraj glavne funkcije in funkcije povratnega klica. Če poskusimo nekaj takega:

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

rezultat tiskanja bi bil nedefiniran, ker programi ne čakajo na izvedbo funkcije searchOccurrences. Premakne se na naslednje navodilo, ki je izjava za tiskanje, preden je spremenljivka rezultata dodeljena znotraj glavne funkcije. Posledično bomo natisnili nedodeljeno spremenljivko rezultata.

Na podlagi te logike bi torej morali ohraniti vso kodo, ki uporablja spremenljivko rezultata, znotraj funkcije povratnega klica. To se zdaj morda ne zdi težava, a lahko hitro preraste v resnično težavo. Predstavljajte si primer, ko imamo verigo asinhronih funkcij, ki se morajo izvajati v zaporedju. V tipični logiki povratnega klica bi implementirali nekaj takega:

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

Ne pozabite, da ima vsak povratni klic parameter napake in da je treba vsako napako obravnavati posebej. Zaradi tega je že tako zapletena koda zgoraj še bolj zapletena in težka za vzdrževanje. Upajmo, da so Promises tukaj, da rešijo problem povratnega klica, to bomo obravnavali naslednjič.

Obljube

Obljube so zgrajene na podlagi povratnih klicev in delujejo na podoben način. Predstavljeni so bili kot del funkcij ES6 za rešitev nekaterih očitnih težav s povratnimi klici, kot je pekel povratnega klica. Obljube zagotavljajo lastne funkcije, ki se izvajajo ob uspešnem zaključku (razreši) in ko pride do napak (zavrni). Naslednje prikazuje primer searchOccurrences, implementiran z obljubami:

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

Oglejmo si spremembe, ki smo jih uporabili:

Funkcija searchOccurrences vrne obljubo. Znotraj obljube ohranjamo isto logiko: imamo dve funkciji razreši in zavrni, ki predstavljata naše povratne klice, namesto ene same funkcije povratnega klica, ki obravnava tako uspešno izvedbo kot izvedbo z napakami. Obljube ločujejo oba rezultata in zagotavljajo čisto sintakso pri klicu glavne funkcije. Funkcija razrešitve je "priklopljena" na glavno funkcijo s ključno besedo "potem". Tukaj samo določimo dva parametra funkcije razrešitve in natisnemo rezultat iskanja. Podobno velja za zavrnitveno funkcijo, ki jo je mogoče zaskočiti s ključno besedo »catch«. Upajmo, da boste znali ceniti prednosti, ki jih ponujajo obljube glede berljivosti in čistoče kode. Če še vedno razpravljate o tem, preverite, kako lahko rešimo problem povratnega klica tako, da povežemo asinhrone funkcije, da se izvajajo ena za drugo:

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

Async/Await

Async/Await sta najnovejši dodatek k našemu asinhronemu orodnemu pasu v Javascriptu. Predstavljeni z ES8 zagotavljajo novo plast abstrakcije poleg asinhronih funkcij tako, da preprosto "čakajo" na izvedbo asinhrone operacije. Tok programa se blokira pri tem navodilu, dokler ni vrnjen rezultat iz asinhrone operacije, nato pa program nadaljuje z naslednjim navodilom. Če razmišljate o sinhronem toku izvajanja, imate prav. Prišli smo do konca! Async/await poskuša prenesti preprostost sinhronega programiranja v asinhroni svet. Upoštevajte, da je to zaznano le pri izvajanju in kodi programa. Pod pokrovom ostaja vse enako, Async/await še vedno uporabljata obljube in povratni klici so njihovi gradniki.

Oglejmo si naš primer in ga implementirajmo z uporabo 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}`);

Naša koda se ni veliko spremenila, pomembno je, da tukaj opazite ključno besedo »async« pred deklaracijo funkcije searchOccurrences. To pomeni, da je funkcija asinhrona. Upoštevajte tudi ključno besedo »await«, ko kličete funkcijo searchOccurrences. To bo programu ukazalo, naj počaka na izvedbo funkcije, dokler se ne vrne rezultat, preden se program lahko premakne na naslednje navodilo, z drugimi besedami, spremenljivka rezultata bo vedno vsebovala vrnjeno vrednost funkcije searchOccurrences in ne obljube funkcija v tem smislu Async/Await nima čakajočega stanja kot Promises. Ko je izvedba končana, se premaknemo na stavek za tiskanje in tokrat rezultat dejansko vsebuje rezultat iskalne operacije. Kot je bilo pričakovano, se nova koda obnaša enako, kot če bi bila sinhrona.

Še ena manjša stvar, ki jo morate imeti v mislih, je, da moramo, ker nimamo več funkcij za povratni klic, obravnavati napako searchOccurrences znotraj iste funkcije, saj napake ne moremo preprosto prenesti na funkcijo za povratni klic in jo tam obravnavati. Tukaj samo natisnemo sporočilo o napaki v primeru napake zaradi primera.

Zaviti

V tem članku smo pregledali različne pristope, ki se uporabljajo za implementacijo asinhrone logike v Javascript. Začeli smo z raziskovanjem konkretnega primera, zakaj bi morali preiti z običajnega sinhronega sloga programiranja na asinhroni model. Nato smo prešli na povratne klice, ki so glavni gradniki asinhronega Javascripta. Omejitve povratnih klicev so nas pripeljale do različnih alternativ, ki so bile dodane z leti za premagovanje teh omejitev, predvsem obljub in Async/await. Asinhrono logiko je mogoče najti kjer koli v spletu, ne glede na to, ali kličete zunanji API, sprožate poizvedbo po bazi podatkov, pišete v lokalni datotečni sistem ali celo čakate na uporabniški vnos v obrazec za prijavo. Upajmo, da ste zdaj bolj samozavestni, da se boste spoprijeli s temi težavami s pisanjem čistega in vzdrževanega asinhronega Javascripta!

Če vam je ta članek všeč, si oglejte Blog CLA, kjer razpravljamo o različnih temah o tem, kako vstopiti v tehnologijo. Oglejte si tudi naš youtube kanal za naše prejšnje brezplačne delavnice in nas spremljajte na družabnih medijih, da ne boste zamudili prihajajočih!


Career Services background pattern

Karierne storitve

Contact Section background image

Ostanimo v stiku

Code Labs Academy © 2024 Vse pravice pridržane.