Kung nagsisimula ka pa lamang sa programming, malamang, iniisip mo ang tungkol sa mga programa bilang isang hanay ng mga sunud-sunod na bloke ng lohika, kung saan ang bawat bloke ay gumagawa ng isang tiyak na bagay at ipinapasa ang resulta nito upang ang susunod na bloke ay maaaring tumakbo at iba pa, at para sa karamihan sa bahagi ay tama ka, karamihan sa mga programa ay tumatakbo sa sunud-sunod na paraan, ang modelong ito ay nagpapahintulot sa amin na bumuo ng mga programa na simpleng isulat at mapanatili. May mga partikular na kaso ng paggamit gayunpaman kung saan ang sequential model na ito ay hindi gagana, o hindi magiging pinakamainam. Bilang halimbawa, isaalang-alang ang isang application ng book reader. Ang application na ito ay may ilang mga advanced na tampok tulad ng paghahanap ng lahat ng paglitaw ng isang salita, pag-navigate sa pagitan ng mga bookmark, at katulad. Ngayon isipin na ang gumagamit ay kasalukuyang nagbabasa ng isang mahabang libro at nagpasya na hanapin ang lahat ng mga paglitaw ng isang karaniwang salita tulad ng "Ang". Karaniwang tatagal ng ilang segundo ang application upang mahanap at mai-index ang lahat ng paglitaw ng salitang iyon. Sa isang sunud-sunod na programa, hindi maaaring makipag-ugnayan ang user sa application (baguhin ang pahina o i-highlight ang isang teksto) hanggang sa matupad ang operasyon ng paghahanap. Sana, makikita mo na hindi iyon pinakamainam na karanasan ng user!
Ang diagram ay naglalarawan ng isang tipikal na daloy ng pagpapatupad ng application ng book reader. Kung ang user ay nagpasimula ng isang matagal na operasyon (sa kasong ito ang paghahanap para sa lahat ng paglitaw ng "ang" sa isang malaking libro), ang application ay "nag-freeze" para sa lahat ng tagal ng operasyon na iyon. Sa kasong ito, patuloy na magki-click ang user sa susunod na button ng bookmark na walang resulta hanggang sa matapos ang operasyon ng paghahanap at magkakabisa ang lahat ng operasyon nang sabay-sabay na nagbibigay sa end user ng pakiramdam ng isang lagging application.
Maaaring napansin mo na ang halimbawang ito ay hindi talaga tumutugma sa sequential model na ipinakilala namin kanina. Ito ay dahil ang mga operasyon dito ay independyente sa bawat isa. Hindi kailangang malaman ng user ang tungkol sa bilang ng mga paglitaw ng "ang" upang mag-navigate sa susunod na bookmark, kaya ang pagkakasunud-sunod ng pagpapatupad ng mga operasyon ay hindi talaga mahalaga. Hindi namin kailangang hintayin ang pagtatapos ng operasyon ng paghahanap bago kami makapag-navigate sa susunod na bookmark. Ang isang posibleng pagpapabuti sa nakaraang daloy ng pagpapatupad ay batay sa lohika na ito: maaari naming patakbuhin ang mahabang operasyon sa paghahanap sa background, magpatuloy sa anumang mga papasok na operasyon, at kapag tapos na ang mahabang operasyon, maaari na lang naming ipaalam sa gumagamit. Ang daloy ng pagpapatupad ay nagiging tulad ng sumusunod:
Sa daloy ng pagpapatupad na ito, ang karanasan ng user ay makabuluhang napabuti. Ngayon ang user ay maaaring magpasimula ng isang matagal na operasyon, magpatuloy sa paggamit ng application nang normal, at maabisuhan kapag ang operasyon ay tapos na. Ito ang batayan ng asynchronous programming.
Ang Javascript, bukod sa iba pang mga wika, ay sumusuporta sa istilong ito ng asynchronous na programming sa pamamagitan ng pagbibigay ng malawak na mga API upang makamit ang halos anumang asynchronous na gawi na maiisip mo. Sa pagtatapos ng araw, ang Javascript ay dapat na likas na isang asynchronous na wika. Kung sasangguni tayo sa nakaraang halimbawa, ang asynchronous na logic ay nasa base ng lahat ng application ng user-interaction, at ang Javascript ay pangunahing binuo para magamit sa browser kung saan ang karamihan sa mga program ay tungkol sa pagtugon sa mga aksyon ng user.
Ang sumusunod ay magbibigay sa iyo ng maikling gabay sa Asynchronous Javascript:
Mga callback
Sa isang tipikal na programa, karaniwan mong mahahanap ang isang bilang ng mga function. Upang gumamit ng isang function, tinatawag namin ito ng isang hanay ng mga parameter. Ang function code ay magpapatupad at magbabalik ng isang resulta, walang kakaiba. Ang asynchronous programming ay bahagyang nagbabago sa lohika na ito. Kung babalikan ang halimbawa ng application ng book reader, hindi kami makakagamit ng regular na function upang ipatupad ang logic ng operasyon sa paghahanap dahil ang operasyon ay tumatagal ng hindi kilalang tagal ng oras. Ang isang regular na function ay karaniwang babalik bago ang operasyon ay tapos na, at hindi ito ang pag-uugali na aming inaasahan. Ang solusyon ay upang tukuyin ang isa pang function na isasagawa kapag ang paghahanap na operasyon ay tapos na. Ito ay modelo ng aming kaso ng paggamit dahil ang aming programa ay maaaring magpatuloy sa daloy nito nang normal at sa sandaling ang operasyon ng paghahanap ay tapos na, ang tinukoy na function ay isasagawa upang ipaalam sa gumagamit ang mga resulta ng paghahanap. Ang function na ito ay tinatawag nating callback function:
// 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);
Una, tinukoy namin ang function ng paghahanap ng operasyon, searchOccurrences. Kinakailangan ang salitang hahanapin at ang pangalawang parameter na "callback" na magiging function na isasagawa kapag tapos na ang paghahanap. Ang function ng search operation ay sadyang pinananatiling abstract, kailangan lang nating tumuon sa dalawang posibleng resulta nito: ang unang kaso ay kung saan naging matagumpay ang lahat at mayroon tayong resulta ng paghahanap sa variable ng resulta. Sa kasong ito, kailangan lang nating tawagan ang callback function na may mga sumusunod na parameter: ang unang parameter ay null na nangangahulugang walang naganap na error, ang pangalawang parameter ay ang salitang hinanap, at ang pangatlo at marahil pinakamahalagang parameter ng tatlo., ay ang resulta ng operasyon sa paghahanap.
Ang pangalawang kaso ay kung saan naganap ang isang error, ito rin ay isang kaso kung saan ang pagpapatupad ng operasyon ng paghahanap ay tapos na at kailangan nating tawagan ang callback function. Gumagamit kami ng try and catch block para ma-intercept ang anumang error at tinatawag lang namin ang callback function na may error object mula sa catch block.
Pagkatapos ay tinukoy namin ang function ng callback, handleSearchOccurrences, pinananatiling simple namin ang lohika nito. Ito ay isang bagay lamang ng pag-print ng isang mensahe sa console. Sinusuri muna namin ang parameter na "err" upang makita kung may anumang error na naganap sa pangunahing function. Sa ganoong sitwasyon, ipinapaalam lang namin sa user na natapos sa isang error ang operasyon sa paghahanap. Kung walang mga error na itinaas, nag-print kami ng isang mensahe na may resulta ng operasyon sa paghahanap.
Panghuli, tinatawag namin ang searchOccurrences function na may salitang "ang". Ang function ay tatakbo na ngayon nang normal nang hindi hinaharangan ang pangunahing programa at sa sandaling ang paghahanap ay tapos na, ang callback ay isasagawa at makukuha namin ang mensahe ng resulta alinman sa resulta ng paghahanap o ang mensahe ng error.
Mahalagang banggitin dito na mayroon lang kaming access sa variable ng resulta sa loob ng pangunahing at ang mga function ng callback. Kung susubukan natin ang isang bagay tulad nito:
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);
ang resulta ng pag-print ay hindi matukoy dahil ang mga programa ay hindi naghihintay para sa searchOccurrences function na isagawa. Lumipat ito sa susunod na pagtuturo na siyang print statement bago italaga ang variable ng resulta sa loob ng pangunahing function. Bilang resulta, ipi-print namin ang hindi naitalagang variable ng resulta.
Kaya batay sa lohika na ito, dapat nating panatilihin ang lahat ng code na gumagamit ng variable ng resulta sa loob ng function ng callback. Maaaring hindi ito mukhang problema ngayon ngunit maaari itong mabilis na lumaki sa isang tunay na problema. Isipin ang kaso kung saan mayroon kaming isang chain ng mga asynchronous na function na kailangang tumakbo nang sunud-sunod, Sa karaniwang callback logic, ipapatupad mo ang isang bagay na tulad nito:
functionA(function (err, resA) {
///......
functionB(resA, function (err, resB) {
///......
functionC(resB, function (err, resC) {
///......
functionD(resC, function (err, resD) {
///......
});
});
});
});
Tandaan na ang bawat callback ay may parameter ng error at ang bawat error ay kailangang hawakan nang hiwalay. Ginagawa nitong mas kumplikado at mahirap pangalagaan ang dati nang kumplikadong code sa itaas. Sana, narito ang Mga Pangako upang malutas ang problema sa impiyerno ng callback, tatalakayin natin iyon sa susunod.
Mga pangako
Ang mga pangako ay binuo sa ibabaw ng mga callback at gumagana sa katulad na paraan. Ipinakilala ang mga ito bilang bahagi ng mga feature ng ES6 upang malutas ang ilan sa mga nakakasilaw na isyu sa mga callback gaya ng callback hell. Ang mga pangako ay nagbibigay ng sarili nilang mga function na tumatakbo sa matagumpay na pagkumpleto (resolve), at kapag naganap ang mga error (reject). Ang sumusunod ay nagpapakita ng halimbawa ng searchOccurrences na ipinatupad na may mga pangako:
// 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`);
});
Balikan natin ang mga pagbabagong inilapat namin:
Ang function na searchOccurrences ay nagbabalik ng isang pangako. Sa loob ng pangako, pinananatili namin ang parehong lohika: mayroon kaming dalawang function na niresolba at tinatanggihan na kumakatawan sa aming mga callback sa halip na magkaroon ng isang function ng callback na humahawak sa parehong matagumpay na pagpapatupad at isang pagpapatupad na may mga error. Pinaghihiwalay ng mga pangako ang dalawang kinalabasan at nagbibigay ng malinis na syntax kapag tumatawag sa pangunahing function. Ang paglutas ng function ay "nakakabit" sa pangunahing function gamit ang "pagkatapos" na keyword. Dito ay tinukoy lamang namin ang dalawang parameter ng pag-andar ng paglutas at i-print ang resulta ng paghahanap. Ang isang katulad na bagay ay nalalapat sa pag-andar ng pagtanggi, maaari itong mai-hook gamit ang keyword na "catch". Sana, maaari mong pahalagahan ang mga bentahe na iniaalok ng mga pangako sa mga tuntunin ng pagiging madaling mabasa at kalinisan ng code. Kung pinagtatalunan mo pa rin ito, tingnan kung paano namin malulutas ang callback na problema sa impiyerno sa pamamagitan ng pagsasama-sama ng mga asynchronous na function upang tumakbo nang sunud-sunod:
searchOccurrences("the")
.then(searchOccurrences("asynchronous"))
.then(searchOccurrences("javascript"))
.then(searchOccurrences("guide"))
.catch((err) => {
console.log(`Search operation ended with an error`);
});
Async/Await
Ang Async/Await ay ang pinakabagong karagdagan sa aming asynchronous na toolbelt sa Javascript. Ipinakilala sa ES8, nagbibigay sila ng bagong layer ng abstraction sa ibabaw ng mga asynchronous na function sa pamamagitan lamang ng "paghihintay" para sa pagpapatupad ng isang asynchronous na operasyon. Ang daloy ng programa ay humaharang sa pagtuturo na iyon hanggang sa maibalik ang isang resulta mula sa asynchronous na operasyon at pagkatapos ay magpapatuloy ang programa sa susunod na pagtuturo. Kung iniisip mo ang kasabay na daloy ng pagpapatupad, tama ka. Buong bilog na kami! Mga pagtatangka ng Async/wait na dalhin ang pagiging simple ng synchronous programming sa asynchronous na mundo. Mangyaring tandaan na ito ay nakikita lamang sa pagpapatupad at sa code ng programa. Ang lahat ay nananatiling pareho sa ilalim ng hood, Async/wait ay gumagamit pa rin ng mga pangako at mga callback ang kanilang mga bloke ng gusali.
Tingnan natin ang aming halimbawa at ipatupad ito gamit ang Async/wait:
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}`);
Hindi gaanong nagbago ang aming code, ang mahalagang mapansin dito ay ang "async" na keyword bago ang deklarasyon ng function ng searchOccurrences. Ito ay nagpapahiwatig na ang function ay asynchronous. Gayundin, pansinin ang keyword na "naghihintay" kapag tumatawag sa function na searchOccurrences. Ito ay magtuturo sa programa na maghintay para sa pagpapatupad ng function hanggang sa maibalik ang resulta bago ang programa ay maaaring lumipat sa susunod na pagtuturo, sa madaling salita, ang variable ng resulta ay palaging hawak ang ibinalik na halaga ng searchOccurrences function at hindi ang pangako ng ang function, sa kahulugang iyon, ang Async/Await ay walang nakabinbing estado bilang Mga Pangako. Kapag tapos na ang pagpapatupad, lumipat kami sa print statement at sa pagkakataong ito ang resulta ay aktwal na naglalaman ng resulta ng operasyon sa paghahanap. Tulad ng inaasahan, ang bagong code ay may parehong pag-uugali na parang ito ay kasabay.
Ang isa pang maliit na bagay na dapat tandaan ay dahil wala na kaming mga callback function, kailangan naming pangasiwaan ang searchOccurrences error sa loob ng parehong function dahil hindi lang namin maaaring i-propagate ang error sa callback function at hawakan ito doon. Narito kami ay nagpi-print lamang ng isang mensahe ng error sa kaso ng isang error para sa kapakanan ng halimbawa.
Balutin
Sa artikulong ito, dumaan kami sa iba't ibang mga diskarte na ginamit upang ipatupad ang asynchronous logic sa Javascript. Nagsimula kami sa pamamagitan ng paggalugad ng isang kongkretong halimbawa kung bakit kailangan naming lumipat mula sa regular na magkakasabay na istilo ng programming patungo sa asynchronous na modelo. Pagkatapos ay lumipat kami sa mga callback, na siyang pangunahing mga bloke ng gusali ng asynchronous na Javascript. Ang mga limitasyon ng mga callback ay humantong sa amin sa iba't ibang mga alternatibo na idinagdag sa mga nakaraang taon upang malampasan ang mga limitasyong ito, pangunahin ang mga pangako at Async/naghihintay. Matatagpuan ang asynchronous logic kahit saan sa web, tumatawag ka man sa isang panlabas na API, nagpapasimula ng query sa database, sumulat sa lokal na filesystem, o kahit na naghihintay para sa input ng user sa isang login form. Sana, mas kumpiyansa ka na ngayong harapin ang mga isyung ito sa pamamagitan ng pagsusulat ng malinis at napapanatiling Asynchronous Javascript!
Kung gusto mo ang artikulong ito, pakitingnan ang CLA Blog kung saan tinatalakay namin ang iba't ibang paksa kung paano makapasok sa tech. Gayundin, tingnan ang aming youtube channel para sa aming mga nakaraang libreng workshop at sundan kami sa social media para hindi ka makaligtaan sa mga paparating!
Patunay sa hinaharap ang iyong karera sa pamamagitan ng pagpapahusay sa HTML, CSS, at JavaScript gamit ang [Web Development Bootcamp] ni Code Labs Academy(/courses/web-development).