Programazioan hasi baino ez bazara, litekeena da programak logika-bloke sekuentzialen multzo gisa pentsatzen ari zarela, non bloke bakoitzak gauza zehatz bat egiten duen eta bere emaitza pasatzen duen hurrengo blokea exekutatu ahal izateko eta abar, eta gehienetan arrazoi duzu, programa gehienak modu sekuentzialean exekutatzen dira, eredu honek idazteko eta mantentzeko errazak diren programak eraikitzeko aukera ematen digu. Hala ere, eredu sekuentzial honek funtzionatuko ez lukeen edo optimoa izango ez den erabilera kasu zehatzak daude. Adibide gisa, kontuan hartu liburuak irakurtzeko aplikazio bat. Aplikazio honek funtzio aurreratu batzuk ditu, esate baterako, hitz baten agerraldi guztiak aurkitzea, laster-marken artean nabigatzea eta antzekoak. Orain imajinatu erabiltzailea liburu luze bat irakurtzen ari dela eta "The" bezalako hitz arrunt baten agerraldi guztiak bilatzea erabakitzen duela. Aplikazioak normalean segundo pare bat beharko ditu hitz horren agerraldi guztiak aurkitzeko eta indexatzeko. Programa sekuentzial batean, erabiltzaileak ezin du aplikazioarekin elkarreragin (orria aldatu edo testu bat nabarmendu) bilaketa-eragiketa bete arte. Zorionez, ikus dezakezu hori ez dela erabiltzailearen esperientzia optimoa!
Diagramak liburu irakurgailuaren aplikazioaren exekuzio-fluxu tipiko bat erakusten du. Erabiltzaileak iraupen luzeko eragiketa bat hasten badu (kasu honetan liburu handi batean "the"-ren agerraldi guztien bilaketa), aplikazioa "izoztu" egiten da eragiketa horrek irauten duen bitartean. Kasu honetan, erabiltzaileak hurrengo laster-marka botoian klik egiten jarraituko du emaitzarik gabe bilaketa-eragiketa amaitu arte eta eragiketa guztiak aldi berean sartuko dira indarrean azken erabiltzaileari aplikazio atzeratu baten sentipena emanez.
Konturatuko zinen adibide hau ez datorrela bat lehenago aurkeztu dugun eredu sekuentzialari. Hau da, hemengo eragiketak elkarrengandik independenteak direlako. Erabiltzaileak ez du "the"-ren agerraldi kopuruaren berri izan behar hurrengo laster-markara nabigatzeko, beraz, eragiketen exekuzio-ordena ez da oso garrantzitsua. Ez dugu bilaketa-eragiketaren amaierara itxaron behar hurrengo laster-markara nabigatu ahal izateko. Aurreko exekuzio-fluxuaren hobekuntza posible bat logika honetan oinarritzen da: bilaketa-eragiketa luzea atzeko planoan exekutatu dezakegu, sarrerako edozein eragiketekin jarraitu, eta eragiketa luzea egin ondoren, erabiltzaileari jakinaraztea besterik ez dugu egin. Exekuzio-fluxua honela bihurtzen da:
Exekuzio-fluxu honekin, erabiltzailearen esperientzia nabarmen hobetzen da. Orain, erabiltzaileak iraupen luzeko eragiketa bat has dezake, aplikazioa normalean erabiltzen jarraitu eta eragiketa amaitutakoan jakinarazpena jaso dezake. Hau da programazio asinkronoaren oinarria.
Javascript-ek, beste hizkuntza batzuen artean, programazio asinkronoaren estilo hau onartzen du API zabalak eskainiz, bururatzen zaizun portaera asinkronoa lortzeko. Azken finean, Javascript-ek berez hizkuntza asinkronoa izan beharko luke. Aurreko adibideari erreferentzia egiten badiogu, logika asinkronoa erabiltzailearen interakziorako aplikazio guztien oinarrian dago, eta Javascript nagusiki nabigatzailean erabiltzeko eraiki zen, non programa gehienak erabiltzailearen ekintzei erantzuteko diren.
Honako hauek Javascript asinkronoari buruzko gida labur bat emango dizu:
Deiak
Programa tipiko batean, normalean, hainbat funtzio aurkituko dituzu. Funtzio bat erabiltzeko, parametro multzo batekin deitzen diogu. Funtzio-kodeak emaitza bat exekutatu eta itzuliko du, ohikoa ez den ezer. Programazio asinkronoak logika hori apur bat aldatzen du. Liburuak irakurtzeko aplikazioaren adibidera itzuliz, ezin dugu funtzio arrunt bat erabili bilaketa-eragiketen logika ezartzeko eragiketak denbora ezezagun bat hartzen baitu. Funtzio arrunt bat funtsean itzuliko da eragiketa egin baino lehen, eta hau ez da espero dugun portaera. Irtenbidea bilaketa-eragiketa egin ondoren exekutatuko den beste funtzio bat zehaztea da. Honek gure erabilera-kasua modelatzen du, gure programak bere fluxua normal jarraitu dezakeelako eta bilaketa-eragiketa amaitutakoan, zehaztutako funtzioa exekutatuko da erabiltzaileari bilaketa-emaitzen berri emateko. Funtzio hau callback funtzioa deitzen dioguna da:
// 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);
Lehenik eta behin, bilaketa-eragiketa funtzioa definitzen dugu, searchOccurrences. Bilatzeko hitza eta "callback" bigarren parametro bat hartzen ditu, bilaketa-eragiketa egin ondoren exekutatzeko funtzioa izango dena. Bilaketa-eragiketa funtzioa nahita abstraktu mantendu zen, bere bi emaitza posibleetan bakarrik zentratu behar dugu: lehenengo kasua dena arrakastatsua izan zen eta bilaketaren emaitza emaitza aldagaian dugu. Kasu honetan, callback funtzioari parametro hauekin deitzea besterik ez dugu egin behar: lehen parametroa nulua da, erran nahi baita errorerik gertatu ez dela, bigarren parametroa bilatu den hitza da eta hirugarren parametroa eta agian garrantzitsuena hiruretan., bilaketa-eragiketaren emaitza da.
Bigarren kasua errore bat gertatzen da, hau ere bilaketa-eragiketaren exekuzioa egiten den kasua da eta callback funtzioari deitu behar diogu. Try and catch bloke bat erabiltzen dugu edozein errore atzemateko eta callback funtzioari deitzen diogu errore objektuarekin catch bloketik.
Ondoren, callback funtzioa definitu dugu, handleSearchOccurrences, bere logika nahiko sinplea mantendu dugu. Kontsolara mezu bat inprimatzea besterik ez da. Lehenik eta behin "err" parametroa egiaztatzen dugu funtzio nagusian errorerik gertatu den ikusteko. Kasu horretan, bilaketa-eragiketa akats batekin amaitu dela jakinarazten diogu erabiltzaileari. Akatsik sortzen ez bada, bilaketa-eragiketaren emaitzarekin mezu bat inprimatuko dugu.
Azkenik, searchOccurrences funtzioari "the" hitzarekin deitzen diogu. Funtzioa orain normal abiaraziko da programa nagusia blokeatu gabe eta behin bilaketa eginda, deia itzultzea exekutatuko da eta emaitza-mezua jasoko dugu bilaketaren emaitzarekin edo errore-mezuarekin.
Garrantzitsua da hemen aipatzea emaitza-aldagairako sarbidea bakarrik daukagula funtzio nagusien eta dei-itzuleraren barruan. Horrelako zerbait saiatzen bagara:
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);
inprimaketaren emaitza zehaztu gabe geratuko litzateke, programek ez baitute itxaron searchOccurrences funtzioa exekutatu arte. Funtzio nagusiaren barruan emaitza aldagaia esleitu baino lehen inprimatzeko instrukziora igarotzen da hurrengo instrukziora. Ondorioz, esleitu gabeko emaitza aldagaia inprimatuko dugu.
Beraz, logika honetan oinarrituta, emaitza-aldagaia erabiltzen duen kode guztia callback funtzioaren barruan gorde beharko genuke. Agian ez dirudi arazo bat, baina azkar handitu daiteke benetako arazo batera. Imajinatu sekuentzian exekutatu behar diren funtzio asinkronoen kate bat daukagun kasua, dei-itzulerako logika tipikoan, honelako zerbait ezarriko zenuke:
functionA(function (err, resA) {
///......
functionB(resA, function (err, resB) {
///......
functionC(resB, function (err, resC) {
///......
functionD(resC, function (err, resD) {
///......
});
});
});
});
Kontuan izan itzulera bakoitzak errore-parametro bat duela eta errore bakoitza bereizita kudeatu behar dela. Horrek goiko kode konplexua are konplexuagoa eta mantentzen zailagoa bihurtzen du. Zorionez, Promesak hemen daude callback infernuko arazoa konpontzeko, hurrengoan landuko dugu.
Promesak
Promesak deien itzulketen gainean eraikitzen dira eta antzeko moduan funtzionatzen dute. ES6 funtzioen zati gisa sartu ziren, dei-itzulketen infernua bezalako arazo nabarmen batzuk konpontzeko. Promesek beren funtzioak eskaintzen dituzte arrakastaz amaitzean (konpontzen) eta akatsak gertatzen direnean (baztertu). Jarraian, promesekin inplementatutako searchOccurrences adibidea erakusten da:
// 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`);
});
Ikus ditzagun aplikatu ditugun aldaketak:
SearchOccurrences funtzioak promesa bat itzultzen du. Promesaren barruan, logika bera mantentzen dugu: gure deiak adierazten dituzten bi funtzio ebatzi eta baztertzen ditugu, exekuzio arrakastatsua eta akatsak dituen exekuzioa kudeatzen dituen dei-itzulera funtzio bakarra eduki beharrean. Promesek bi emaitzak bereizten dituzte eta sintaxi garbia ematen dute funtzio nagusia deitzean. Ebazteko funtzioa funtzio nagusiari "lokatuta" dago "orduan" gako-hitza erabiliz. Hemen ebazteko funtzioaren bi parametroak zehaztu eta bilaketaren emaitza inprimatuko dugu. Antzeko gauza bat aplikatzen da baztertzeko funtzioari, "catch" gako-hitza erabiliz lotu daiteke. Zorionez, promesek kodeen irakurgarritasunari eta garbitasunari dagokionez eskaintzen dituzten abantailak baloratu ditzakezu. Oraindik eztabaidatzen ari bazara, begiratu nola ebatzi dezakegun callback infernuaren arazoa funtzio asinkronoak bata bestearen atzetik exekutatzeko kateatuz:
searchOccurrences("the")
.then(searchOccurrences("asynchronous"))
.then(searchOccurrences("javascript"))
.then(searchOccurrences("guide"))
.catch((err) => {
console.log(`Search operation ended with an error`);
});
Async/Itxaron
Async/Await gure tresna asinkronoaren azken gehikuntza dira Javascript-en. ES8-rekin sartuta, funtzio asinkronoen gainean abstrakzio geruza berri bat eskaintzen dute, eragiketa asinkrono baten exekuzioaren "itxaroten" besterik gabe. Programaren fluxua instrukzio horretan blokeatzen da eragiketa asinkronotik emaitza bat itzultzen den arte eta, ondoren, programak hurrengo instrukzioarekin jarraituko du. Exekuzio-fluxu sinkronikoan pentsatzen ari bazara zuzen zaude. Zirkulu osoa egin dugu! Async/wait programazio sinkronoaren sinpletasuna mundu asinkronora ekartzen saiatzen da. Kontuan izan hau programaren exekuzioan eta kodean bakarrik hautematen dela. Dena berdin mantentzen da kanpaiaren azpian, Async/wait-ek promesak erabiltzen jarraitzen dute eta deiak dira haien eraikuntza-blokeak.
Ikus dezagun gure adibidea eta inplementatu Async/wait erabiliz:
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}`);
Gure kodea ez da asko aldatu, hemen kontutan hartu beharreko gauza "async" gako-hitza da searchOccurrences funtzioaren deklarazioaren aurretik. Horrek funtzioa asinkronoa dela adierazten du. Era berean, konturatu "itxaron" gako-gakoari searchOccurrences funtzioari deitzean. Honek programari aginduko dio funtzioaren exekuzioaren arte itxaron behar duela emaitza itzultzen den arte programa hurrengo instrukziora joan baino lehen, hau da, emaitza aldagaiak beti edukiko du searchOccurrences funtzioaren itzultzen den balioa eta ez promesa. funtzioak, zentzu horretan, Async/Await-ek ez du Promises gisa zain dagoen egoerarik. Exekuzioa amaitutakoan, inprimatzeko instrukziora mugitzen gara eta oraingoan emaitzak bilaketa-eragiketaren emaitza dauka. Espero zen bezala, kode berriak sinkronoa izango balitz bezalako portaera du.
Kontuan izan behar den beste gauza txiki bat da jada deitzeko funtziorik ez dugunez, funtzio beraren barruan searchOccurrences errorea kudeatu behar dugula, ezin dugulako errorea dei-itzulera funtziora hedatu eta bertan kudeatu. Hemen errore-mezu bat inprimatzen ari gara akatsen bat gertatuz gero adibidearen mesedetan.
Biltzea
Artikulu honetan Javascript-en logika asinkronoa ezartzeko erabiltzen diren ikuspegi desberdinak aztertu ditugu. Programazio-estilo sinkrono arruntetik eredu asinkronora aldatu beharko genukeen adibide zehatz bat aztertzen hasi ginen. Ondoren, deietara pasatu ginen, hau da, Javascript asinkronoaren eraikuntza-bloke nagusiak. Deien mugek muga horiek gainditzeko urteetan zehar gehitu ziren alternatiba ezberdinetara eraman gintuzten, batez ere promesak eta Async/wait. Logika asinkronoa sareko edozein lekutan aurki daiteke, kanpoko API bati deitzen ari bazara, datu-basearen kontsulta bat hasiz, fitxategi-sistema lokalean idazten ari zaren edo baita erabiltzaileak saioa hasteko inprimaki batean sarreraren zain egon. Zorionez, orain seguruago sentitzen zara arazo horiei aurre egiteko Javascript asinkrono garbia eta mantentzea idatzita!
Artikulu hau gustatzen bazaizu, begiratu CLA Bloga non teknologian sartzeko hainbat gai eztabaidatzen ditugun. Gainera, begiratu gure youtube kanala aurreko doako tailerretarako eta jarraitu gaitzazu sare sozialetan datozenak ez galtzeko!
Etorkizunean frogatu zure karrera HTML, CSS eta JavaScript-en trebetasunak hobetuz Code Labs Academy-ren Web Garapenaren Bootcamp.