Եթե դուք միայն սկսում եք ծրագրավորումը, ապա հավանականությունը մեծ է, որ դուք մտածում եք ծրագրերի մասին որպես տրամաբանության հաջորդական բլոկների մի շարք, որտեղ յուրաքանչյուր բլոկ անում է որոշակի բան և փոխանցում է իր արդյունքը, որպեսզի հաջորդ բլոկը կարողանա աշխատել և այլն, և մեծ մասամբ դուք ճիշտ եք, ծրագրերի մեծ մասն աշխատում է հաջորդական եղանակով, այս մոդելը մեզ թույլ է տալիս ստեղծել այնպիսի ծրագրեր, որոնք հեշտ է գրել և պահպանել: Կան հատուկ օգտագործման դեպքեր, սակայն, երբ այս հաջորդական մոդելը չի աշխատի կամ օպտիմալ չի լինի: Որպես օրինակ, դիտարկեք գիրք ընթերցող հավելված: Այս հավելվածն ունի մի քանի առաջադեմ գործառույթներ, ինչպիսիք են բառի բոլոր երևույթները գտնելը, էջանիշների միջև նավարկելը և այլն: Հիմա պատկերացրեք, որ օգտատերը ներկայումս երկար գիրք է կարդում և որոշում է փնտրել ընդհանուր բառի բոլոր երևույթները, ինչպիսին է «The»: Հավելվածը սովորաբար տևում է մի քանի վայրկյան՝ գտնելու և ինդեքսավորելու այդ բառի բոլոր երևույթները: Հերթական ծրագրում օգտատերը չի կարող շփվել հավելվածի հետ (փոխել էջը կամ ընդգծել տեքստը), քանի դեռ որոնման գործողությունը չի կատարվել: Հուսով եմ, դուք կարող եք տեսնել, որ դա օպտիմալ օգտագործողի փորձ չէ:
Դիագրամը ցույց է տալիս գիրք ընթերցող հավելվածի տիպիկ կատարողական հոսքը: Եթե օգտատերը նախաձեռնում է երկարատև գործողություն (այս դեպքում մեծ գրքում «the»-ի բոլոր երևույթների որոնումը), ապա հավելվածը «սառեցնում է» այդ գործողության ողջ տևողության ընթացքում: Այս դեպքում օգտատերը կշարունակի սեղմել հաջորդ էջանիշի կոճակի վրա՝ առանց արդյունքի, մինչև որոնման գործողությունն ավարտվի, և բոլոր գործողությունները միանգամից ուժի մեջ կմտնեն՝ վերջնական օգտագործողին տալով ուշացած հավելվածի զգացում:
Դուք կարող եք նկատել, որ այս օրինակն իրականում չի համապատասխանում այն հաջորդական մոդելին, որը մենք ներկայացրել ենք ավելի վաղ: Դա պայմանավորված է նրանով, որ այստեղ գործողությունները միմյանցից անկախ են: Օգտագործողը կարիք չունի իմանալու «the»-ի դեպքերի քանակի մասին՝ հաջորդ էջանիշին նավարկելու համար, ուստի գործողությունների կատարման կարգն իսկապես կարևոր չէ: Մենք չպետք է սպասենք որոնման գործողության ավարտին, նախքան հաջորդ էջանիշին անցնելը: Նախորդ կատարման հոսքի հնարավոր բարելավումը հիմնված է այս տրամաբանության վրա. մենք կարող ենք երկար որոնման գործողությունը կատարել հետին պլանում, շարունակել ցանկացած մուտքային գործողություն, և երբ երկար գործողությունն ավարտվի, մենք կարող ենք պարզապես տեղեկացնել օգտվողին: Կատարման հոսքը դառնում է հետևյալը.
Այս կատարողական հոսքի շնորհիվ օգտվողի փորձը զգալիորեն բարելավվում է: Այժմ օգտատերը կարող է նախաձեռնել երկարատև գործողություն, շարունակել օգտագործել հավելվածը և ստանալ ծանուցում գործողությունն ավարտելուց հետո: Սա ասինխրոն ծրագրավորման հիմքն է։
Javascript-ը, ի թիվս այլ լեզուների, աջակցում է ասինխրոն ծրագրավորման այս ոճին՝ տրամադրելով ընդարձակ API-ներ՝ հասնելու գրեթե ցանկացած ասինխրոն վարքագծի, որի մասին կարող եք մտածել: Ի վերջո, Javascript-ը պետք է իր էությամբ ասինխրոն լեզու լինի: Եթե անդրադառնանք նախորդ օրինակին, ասինքրոն տրամաբանությունը օգտատիրոջ հետ փոխազդեցության բոլոր հավելվածների հիմքում է, և Javascript-ը հիմնականում ստեղծվել է բրաուզերի վրա օգտագործելու համար, որտեղ ծրագրերի մեծ մասը վերաբերում է օգտվողի գործողություններին արձագանքելուն:
Հետևյալը ձեզ կտրամադրի համառոտ ուղեցույց Asynchronous Javascript-ի վերաբերյալ.
Հետզանգեր
Տիպիկ ծրագրում դուք սովորաբար կգտնեք մի շարք գործառույթներ: Ֆունկցիան օգտագործելու համար մենք այն անվանում ենք մի շարք պարամետրերով: Գործառույթի կոդը կգործարկվի և կվերադարձնի արդյունք, ոչ մի արտասովոր բան: Ասինխրոն ծրագրավորումը մի փոքր փոխում է այս տրամաբանությունը: Վերադառնալով գիրք ընթերցող հավելվածի օրինակին, մենք չենք կարող սովորական ֆունկցիա օգտագործել որոնման գործողության տրամաբանությունն իրականացնելու համար, քանի որ գործողությունը անհայտ ժամանակ է պահանջում: Կանոնավոր գործառույթը հիմնականում կվերադառնա նախքան վիրահատությունը կատարելը, և դա այն վարքագիծը չէ, որը մենք ակնկալում ենք: Լուծումն այն է, որ նշվի մեկ այլ գործառույթ, որը կկատարվի որոնման գործողությունն ավարտելուց հետո: Սա մոդելավորում է մեր օգտագործման դեպքը, քանի որ մեր ծրագիրը կարող է նորմալ շարունակել իր հոսքը, և որոնողական գործողությունն ավարտվելուն պես, նշված գործառույթը կկատարվի՝ օգտվողին ծանուցելու որոնման արդյունքների մասին: Այս ֆունկցիան այն է, ինչ մենք անվանում ենք հետադարձ կապի գործառույթ.
// 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);
Նախ սահմանում ենք որոնման գործողության ֆունկցիան՝ searchOccurrences: Որոնման համար անհրաժեշտ է բառը և երկրորդ պարամետրը «հետադարձ զանգ», որը կլինի այն գործառույթը, որը պետք է կատարվի որոնման գործողությունն ավարտելուց հետո: Որոնման գործողության գործառույթը միտումնավոր պահվել է վերացական, մենք միայն պետք է կենտրոնանանք դրա երկու հնարավոր արդյունքների վրա. առաջին դեպքն այն է, երբ ամեն ինչ հաջող է անցել, և մենք ունենք որոնման արդյունքը արդյունքի փոփոխականում: Այս դեպքում մենք պարզապես պետք է կանչենք հետադարձ կապ ֆունկցիան հետևյալ պարամետրերով. առաջին պարամետրը null է, ինչը նշանակում է, որ սխալ տեղի չի ունեցել, երկրորդ պարամետրը որոնված բառն է, և երրորդ և, հավանաբար, ամենակարևոր պարամետրը երեքից:, որոնողական գործողության արդյունքն է։
Երկրորդ դեպքն այն է, երբ տեղի է ունենում սխալ, սա նաև այն դեպքն է, երբ որոնման գործողության կատարումը կատարվում է, և մենք պետք է կանչենք հետ կանչելու գործառույթը։ Մենք օգտագործում ենք «փորձել և բռնել» բլոկը՝ ցանկացած սխալ ընդհատելու համար, և մենք պարզապես կանչում ենք «catch» ֆունկցիան «catch» բլոկի սխալի օբյեկտի հետ:
Այնուհետև մենք սահմանեցինք հետադարձ զանգի գործառույթը՝ handleSearchOccurrences, մենք բավականին պարզ էինք պահում դրա տրամաբանությունը: Խոսքը միայն վահանակի վրա հաղորդագրություն տպելու մասին է: Մենք նախ ստուգում ենք «err» պարամետրը, որպեսզի տեսնենք, թե արդյոք հիմնական գործառույթում որևէ սխալ տեղի է ունեցել: Այդ դեպքում մենք պարզապես օգտատերին հայտնում ենք, որ որոնման գործողությունն ավարտվել է սխալմամբ։ Եթե սխալներ չեն արձանագրվել, մենք տպում ենք հաղորդագրություն որոնման գործողության արդյունքով:
Ի վերջո, մենք անվանում ենք searchOccurrences ֆունկցիան «the» բառով: Ֆունկցիան այժմ կաշխատի նորմալ՝ առանց հիմնական ծրագիրը արգելափակելու, և որոնումն ավարտվելուն պես, հետ կանչը կկատարվի, և մենք կստանանք արդյունքի հաղորդագրությունը կամ որոնման արդյունքով կամ սխալի հաղորդագրությամբ:
Կարևոր է նշել այստեղ, որ մեզ հասանելի է միայն արդյունքի փոփոխականը հիմնական և հետադարձ կապի գործառույթների ներսում: Եթե փորձենք այսպիսի բան.
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);
տպման արդյունքը չի սահմանվի, քանի որ ծրագրերը չեն սպասում, որ կատարվի searchOccurrences ֆունկցիան: Այն տեղափոխվում է հաջորդ հրահանգը, որը տպագիր հայտարարություն է, նախքան հիմնական ֆունկցիայի ներսում արդյունքի փոփոխական նշանակելը: Արդյունքում մենք կտպենք չնշանակված արդյունքի փոփոխականը։
Այսպիսով, հիմնվելով այս տրամաբանության վրա, մենք պետք է պահենք բոլոր կոդը, որն օգտագործում է արդյունքի փոփոխականը հետ կանչելու ֆունկցիայի ներսում: Սա կարող է հիմա խնդիր չթվա, բայց այն կարող է արագ վերածվել իրական խնդրի: Պատկերացրեք այն դեպքը, երբ մենք ունենք ասինխրոն ֆունկցիաների շղթա, որոնք պետք է գործարկվեն հաջորդականությամբ, տիպիկ հետադարձման տրամաբանության մեջ դուք կիրականացնեիք նման բան.
functionA(function (err, resA) {
///......
functionB(resA, function (err, resB) {
///......
functionC(resB, function (err, resC) {
///......
functionD(resC, function (err, resD) {
///......
});
});
});
});
Հիշեք, որ յուրաքանչյուր հետադարձ զանգ ունի սխալի պարամետր, և յուրաքանչյուր սխալ պետք է կարգավորվի առանձին: Սա ավելի բարդ է դարձնում առանց այն էլ բարդ ծածկագիրը վերևում և դժվար է պահպանել: Հուսով ենք, որ Promises-ն այստեղ է, որպեսզի լուծի հետադարձ կապի դժոխքի խնդիրը, մենք դա կանդրադառնանք հաջորդիվ:
Խոստումներ
Խոստումները կառուցվում են հետադարձ զանգերի վրա և գործում են նույն ձևով: Դրանք ներկայացվել են որպես ES6 առանձնահատկությունների մաս՝ հետադարձ զանգերի հետ կապված մի քանի ակնառու խնդիրներ լուծելու համար, ինչպիսին է հետադարձ կապի դժոխքը: Խոստումները ապահովում են իրենց գործառույթները, որոնք գործում են հաջող ավարտի (լուծելու) և սխալների առաջացման դեպքում (մերժում): Հետևյալը ցույց է տալիս խոստումներով իրականացված SearchOccurrences օրինակը.
// 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`);
});
Եկեք անցնենք մեր կիրառած փոփոխություններին.
SearchOccurrences ֆունկցիան խոստում է տալիս: Խոստման ներսում մենք պահպանում ենք նույն տրամաբանությունը. մենք ունենք լուծելու և մերժելու երկու գործառույթ, որոնք ներկայացնում են մեր հետադարձ զանգերը, այլ ոչ թե ունենանք հետադարձ զանգի մեկ գործառույթ, որը կարգավորում է և՛ հաջող կատարումը, և՛ սխալներով կատարումը: Խոստումները բաժանում են երկու արդյունքները և ապահովում են մաքուր շարահյուսություն հիմնական ֆունկցիան կանչելիս: Լուծման ֆունկցիան «կապված» է հիմնական ֆունկցիային՝ օգտագործելով «այնուհետև» հիմնաբառը: Այստեղ մենք պարզապես նշում ենք լուծման ֆունկցիայի երկու պարամետրը և տպում որոնման արդյունքը։ Նմանատիպ բան վերաբերում է մերժման գործառույթին, այն կարող է կեռվել՝ օգտագործելով «catch» հիմնաբառը: Հուսանք, դուք կարող եք գնահատել խոստումների առավելությունները՝ կոդերի ընթերցանության և մաքրության առումով: Եթե դեռ քննարկում եք դրա մասին, տեսեք, թե ինչպես կարող ենք լուծել հետադարձ կապի դժոխքի խնդիրը՝ շղթայելով ասինխրոն ֆունկցիաները՝ մեկը մյուսի հետևից գործելու համար:
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-ը Javascript-ում մեր ասինխրոն գործիքագոտու վերջին լրացումն է: Ներդրված ES8-ով, դրանք ապահովում են աբստրակցիայի նոր շերտ ասինխրոն ֆունկցիաների վերևում՝ պարզապես «սպասելով» ասինխրոն գործողության կատարմանը: Ծրագրի հոսքը արգելափակվում է այդ հրահանգով, մինչև արդյունքը չվերադարձվի ասինխրոն գործողությունից, այնուհետև ծրագիրը կշարունակվի հաջորդ հրահանգով: Եթե դուք մտածում եք կատարման համաժամանակյա հոսքի մասին, ճիշտ եք: Մենք լրիվ շրջան ենք եկել։ Async/wait-ը փորձում է համաժամանակյա ծրագրավորման պարզությունը բերել ասինխրոն աշխարհ: Խնդրում ենք նկատի ունենալ, որ դա ընկալվում է միայն ծրագրի կատարման և կոդում: Ամեն ինչ նույնն է մնում գլխարկի տակ, Async/wait-ը դեռ օգտագործում է խոստումները, իսկ հետադարձ զանգերը դրանց կառուցման բլոկներն են:
Եկեք անցնենք մեր օրինակին և իրականացնենք այն՝ օգտագործելով 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}`);
Մեր կոդը առանձնապես չի փոխվել, այստեղ կարևոր է նկատել «async» հիմնաբառը նախքան searchOccurrences ֆունկցիայի հռչակագիրը: Սա ցույց է տալիս, որ ֆունկցիան ասինխրոն է: Նաև ուշադրություն դարձրեք «սպասում» բանալի բառին, երբ կանչում եք SearchOccurrences ֆունկցիան: Սա կհանձնարարի ծրագրին սպասել ֆունկցիայի կատարմանը, մինչև արդյունքը վերադարձվի, մինչև ծրագիրը կարողանա անցնել հաջորդ հրահանգին, այլ կերպ ասած, արդյունքի փոփոխականը միշտ կպահի searchOccurrences ֆունկցիայի վերադարձված արժեքը և ոչ թե խոստումը: գործառույթը, այդ առումով, Async/Await-ը չունի սպասող վիճակ՝ որպես Promises: Կատարումն ավարտվելուց հետո մենք տեղափոխվում ենք տպագիր հայտարարություն և այս անգամ արդյունքն իրականում պարունակում է որոնման գործողության արդյունքը: Ինչպես և սպասվում էր, նոր կոդը ունի նույն վարքագիծը, կարծես համաժամանակյա լիներ:
Մեկ այլ աննշան բան, որը պետք է հիշել, այն է, որ քանի որ մենք այլևս չունենք հետադարձ կապի գործառույթներ, մենք պետք է կարգավորենք searchOccurrences սխալը նույն ֆունկցիայի ներսում, քանի որ մենք չենք կարող պարզապես տարածել սխալը հետադարձման ֆունկցիայի վրա և կարգավորել այն այնտեղ: Այստեղ մենք պարզապես տպում ենք սխալի հաղորդագրություն՝ հանուն օրինակի։
Ամփոփում
Այս հոդվածում մենք անցանք Javascript-ում ասինխրոն տրամաբանության իրականացման համար օգտագործվող տարբեր մոտեցումների միջով: Մենք սկսեցինք ուսումնասիրելով կոնկրետ օրինակ, թե ինչու պետք է ծրագրավորման կանոնավոր համաժամանակյա ոճից անցնենք ասինխրոն մոդելի: Այնուհետև մենք անցանք հետադարձ զանգերին, որոնք ասինխրոն Javascript-ի հիմնական կառուցողական բլոկներն են: Հետ կանչերի սահմանափակումները մեզ հանգեցրին տարբեր այլընտրանքների, որոնք ավելացվել էին տարիների ընթացքում՝ հաղթահարելու այդ սահմանափակումները, հիմնականում խոստումները և Async/wait-ը: Ասինխրոն տրամաբանությունը կարելի է գտնել համացանցում ցանկացած վայրում՝ անկախ նրանից՝ դուք կանչում եք արտաքին API, նախաձեռնում եք տվյալների բազայի հարցում, գրում եք տեղական ֆայլային համակարգին կամ նույնիսկ սպասում եք օգտատիրոջ մուտքագրմանը մուտքի ձևի վրա: Հուսանք, այժմ դուք ավելի վստահ եք զգում լուծել այս խնդիրները՝ գրելով մաքուր և պահպանվող Asynchronous Javascript:
Եթե ձեզ դուր է գալիս այս հոդվածը, խնդրում ենք ստուգել CLA Blog, որտեղ մենք քննարկում ենք տարբեր թեմաներ, թե ինչպես մտնել տեխնոլոգիա: Նաև տեսեք մեր youtube.com ալիքը մեր նախորդ անվճար սեմինարների համար և հետևեք մեզ սոցիալական լրատվամիջոցներում -labs-academy/), որպեսզի բաց չթողնեք գալիքները:
Ապագայել ձեր կարիերան՝ կատարելագործելով HTML, CSS և JavaScript-ի հմտությունները Code Labs Academy-ի Web Development Bootcamp: