Յուրաքանչյուր ծրագրավորողի կյանքում գալիս է ժամանակ, երբ պետք է պարզել, թե ինչպես է աշխատում ասինքրոն կոդը:
Նրանց համար, ովքեր փորձում են առաջին անգամ հասկանալ, թե ինչպես եւ ինչ է կատարվում այստեղ, երբեմն դա կարող է մի քիչ վախեցնել: Բայց փորձենք հասկանալ: Առաջին հերթին պետք է հասկանալ սինքրոն եւ ասինքրոն կոդերի տարբերությունը։
Ի՞նչ է «ասինքրոն կոդ»- ը:
Ասինքրոնը տեղի է ունենում այն ժամանակ, երբ լինում են առնվազն երկու իրադարձություններ, որոնք տեղի են ունենում տարբեր ժամանակներում: Անդրադառնանք օրինակին.
console.log('hi')
console.log('my name')
console.log('is')
console.log('jeff!')
// Ելք:
// hi
// my name
// is
// jeff!
Այստեղ կոդը կատարվում է ձեր ակնկալած հերթականությամբ: Նման կոդը կոչվում է սինքրոն կոդ։
Մեր գրածի մեծ մասը համարվում է սինքրոն։ Բայց փորձենք հետաձգել այս տողերից մեկի իրականացումը, որպեսզի մյուսները ավելի շուտ կատարվեն : Իսկ ի՞նչ է տեղի ունենում այդ ժամանակ ։
console.log('hi')
console.log('my name')
setTimeout(() => {
console.log('is')
},500)
console.log('jeff!')
// Ելք:
// hi
// my name
// jeff!
// is
Ի՞նչ ստացանք ելքում. այժմ բառերի կարգը խառնված է, իսկ ցանկացած լեզվի համար կարեւոր է բառերի դասավորությունը: Ուստի առաջարկն այլեւս տրամաբանական չէ։ Ի դեպ, կոդը պետք է միաժամանակ աշխատի, սպասի, որ ժամկետը լրանա, եւ շարունակի իրականացումը։ Բայց ոչ այս դեպքում։
Իսկ վերը բերված օրինակում տեղի ունեցավ այն, որ ժամանակի ֆունկցիան՝timeout
-ը կանչված էր կոդի մնացած մասի հետ միաժամանակ, սակայն ներքին log
-ի գործարկումը սկսում է դրանից 500մվ հետո: Այստեղից էլ ելքում սխալ արդյունքի արտատպումը։
Կոդի ինչ-որ մասը կատարվում է հիմնական գործողությունների հերթից առանձին հերթականությամբ։ Սա կոչվում է ասինքրոնություն կամ ասինքրոն կանչ.
Իսկ ինչպե՞ս լուծել այս խնդիրը: Դա անելու համար անհրաժեշտ է մի միջոց, որը կկանգնեցնի ձեր կոդի մնացած մասը, մինչեւ որ ժամանակի ֆունկցիան`timeout
-ը ավարտվի:
Բարեբախտաբար, JavaScript-ն ունի գործիքներ հենց դա անելու համար:
Promise
Պռոմիսները (Promise՝ անգլ․ – խոստում) նման են callback ֆունկցիաներին՝ նույնպես սպասում են ներքին կոդի ավարտին նախքան արժեքի վերադարձնելը։
Տարբերությունն այստեղ այն է, որ երբ ասինքրոն կոդը լրացված է, պռոմիսը վերադարձնում է մի վիճակ, որը թույլ է տալիս կատարել հետագա գործողությունները՝ օգտագործելով այնպիսի մեթոդներ, ինչպիսիք են then()
-ը և catch()
-ը։
Ուշադրություն․ այս հոդվածում մենք շատ մանրամասն չենք վերլուծի պռոմիսները, մեզ համար կարեւոր է ընդհանուր պատկերացում կազմել դրանց մասին։
Քննարկենք հետեւյալ օրինակը։ Ելքը նորից նորմալ է․
const async_func = () => {
return new Promise((resolve,reject) => {
setTimeout(() => {
resolve('is')
},500)
})
}
console.log('hi')
console.log('my name')
async_func().then(res => {
console.log(res);
console.log('jeff!');
}).catch(error => {
console.error(error);
})
// Ելք:
// hi
// my name
// is
// jeff!
Բայց ինչպե՞ս դա տեղի ունեցավ։
Մինչեւ հիմա մենք տեսել ենք, որ պռոմիսները թույլ են տալիս սպասել, մինչեւ որ ասինքրոն կոդն ավարտվի, նախքան մնացած կոդը կկատարվի: Այս օրինակում մենք կրկին վերցրել ենք timeout
ֆունկցիան և ներդրել պռոմիսի մեջ։
timeout
ֆունկցիայի ավարտից հետո պռոմիսը վերադարձնում է resolve
եւ կոդը տեղեկացվում է, որ այն կարող է շարունակել իրականացումը: Պռոմիսը հաջողությամբ կատարելուց հետո մնացած սինքրոն կոդը կիրառում է նույնը, ինչ նախկինում՝ then()
:
Իսկ ի՞նչ կլինի ելքի հետ, եթե resolve
-ի փոխարեն պռոմիսը վերադարձնի reject
։
const async_func = () => {
return new Promise((resolve,reject) => {
setTimeout(() => {
reject(false)
},500)
})
}
console.log('hi')
console.log('my name')
async_func().then(res => {
console.log(res);
console.log('jeff!');
}).catch(error => {
console.error(error);
})
//Ելք:
// hi
// my name
Երբ պռոմիսը վերադարձնում է reject
, կանչվում է catch()
մեթոդը։ Մեր դեպքում այս մեթոդը մուտքագրում է սխալի հաղորդագրությունը եւ դադարեցնում է հաջորդող կոդի կատարումը: Երբ դա տեղի է ունենում, սխալը գրանցելու կարիք չկա:
Այսինքն՝ պայմանականորեն կարող ենք ասել, երբ պռոմիսը վերադարձնում է reject
, մենք շարունակում ենք աշխատել կոդի մնացած մասով, ոչ թե կանգնում ենք սխալի վրա։
Պռոմիսների ներմուծումը մեծապես պարզեցնում է աշխատանքը ասինքրոն կոդով: Սակայն որքան մեծ է պռոմիսների շղթան, այնքան ավելի բարդ եւ դժվար ընկալելի է դարձնում կոդը։
Բայց կա արդյո՞ք ավելի լավ լուծում։
Async/await
JavaScript-ում օգտագործելով async/await
բանալի բառը, մենք կոդը դարձնում ենք ավելի կոնկրետ և ավելի հարմար ընկալման համար։
Տեսնենք, թե ինչ է տեղի ունենում կոդի հետ, երբ ակտիվանում է async/await
-ը:
const async_func = () => {
return new Promise((resolve,reject) => {
setTimeout(() => {
resolve('is');
},530)
});
}
const main = async () => {
console.log('hi')
console.log('my name')
const result = await async_func();
console.log(result);
console.log('jeff!')
}
main();
//Ելք:
// hi
// my name
// is
// jeff!
Հիմա կոդը ավելի լավ տեսք ունի։
Ելքը մնացել է նույնը, ինչ նախկինում։ Սակայն կոդն այդպես գրելը թույլ է տալիս դուրս գալ պռոմիսի շղթայից, այդքանով հանդերձ պահպանելով մարդկանց համար ընկալման հարմարավետությունը։
Նախորդ օրինակից վերցրել ենք նույն ասինքրոն կոդը, միայն այս անգամ նախանշել ենք await
բանալի բառով և պահպանել պռոմիսից ստացած արդյունքը։
Կոդի օգտագործման ժամանակ main
ֆունկցիան սպասում է թե երբ է async_func
-ը վերադարձնելու resolve
,իսկ հետո շարունակում է կոդի մնացած մասի իրականացումը։
Մենք ներդրել ենք նաև սինքրոն կոդի մնացած մասը main()
ֆունկցիայի մեջ և օգտագործել ենք async
բանալի բառը։ Ինչպես promise-ներն ունեն կանոններ, այնպես էլ մենք պետք է հետեւենք async/wait-ի կանոններին, որպեսզի դա աշխատի:
Այդ կանոններն ասում են՝ await
բանալի բառը օգտագործելու համար անհրաժեշտ է դնել այն async
ֆունկցիայի մեջ։ Սրանով ֆունկցիան կարծես տեղեկացնում է, որ իր ներսում սպասվում է ասինքրոնային կոդ:
Ենթադրենք՝ այժմ ուզում ենք մեկ ուրիշ ասինքրոնային կոդ ավելացնել։
Ի՞նչ է լինելու այս դեպքում:
const async_func = () => {
return new Promise((resolve,reject) => {
setTimeout(() => {
resolve('is');
},530)
});
}
const async_func2 = () => {
return new Promise((resolve,reject) => {
setTimeout(() => {
resolve('and I love 2 code');
},500)
});
}
const main = async () => {
console.log('hi')
console.log('my name')
const result = await async_func();
console.log(result);
console.log('jeff!')
const result2 = await async_func2();
console.log(result2)
}
main();
// Ելք:
// hi
// my name
// is
// jeff!
// and I love 2 code
Ուշադրություն դարձրեք, որ այստեղ ավելացնում ենք մեկ այլ ասինքրոն կոդի կտոր եւ ելքը մնում է նույն հերթականությամբ, ինչ որ սպասվում էր:
Տեսեք՝ երկրորդ ասինքրոն ֆունկցիան կանչվում է 30մվ ավելի շուտ, քան առաջինը: Այդ դեպքում ինչո՞ւ կաատարման հերթականությունը չպահպանվեց:
Սինքրոն իրավիճակում երկրորդ ֆունկցիան իսկապես կկանչվեր առաջինից առաջ եւ սխալ ելք տեղի կունենար:
Բայց երբ ակտիավանում է async/await
-ը որը կանգ է առնում առաջին ֆունկցիայի ժամանակ եւ սպասում է 530մվ նախքան resolve
-ի վերադառնալը։ Հետո հասնում է երկրորդ ֆունկցիային եւ 500մվ նույնպես սպասում մինչեւ resolve
-ի վերադարձը։
Ահա թե ինչում է async/await
-ի առավելություն։
Անդրադառնանք ավելի գործնական օրինակին՝ օգտագործելով async/await
.
const getCharacters = () => {
return new Promise((resolve,reject) => {
setTimeout(() => {
const characters = [
'rick',
'jerry',
'morty',
'rick',
'kate',
'summer',
'summer',
'kate',
'noob noob',
'jerry',
'morty jr',
'morty',
'evil morty',
'evil rick',
'noob noob',
'morty jr'
]
resolve(characters);
},300)
})
}
const main = async () => {
const characters = await getCharacters();
const counts = characters.reduce((accumulator,character,index) => {
accumulator[character] ? accumulator[character].count++ :
accumulator[character] = {name: character, count: 1, index: index};
return accumulator;
},{})
const imposters = Object.entries(counts)
.filter(([key,obj]) => obj.count === 1)
.map(([key,obj]) => ({name: key, index: obj.index}))
console.log(imposters);
}
main();
//Ելք:
// [{name: 'evil morty', index: 12},{name: 'evil rick', index: 13}]
Այս օրինակում մենք իմիտացնում ենք backend-ին ուղղված հարցում եւ ստանում ենք այն մարդկանց ցանկը, որոնք անհրաժեշտ է ստուգել impostors
-ի համար:
await
-ը գործարկելուց հետո մենք սպասում ենք, որ main
ֆունկցիան կվերադառնա resolve
պռոմիսով, եւ ի պատասխան ստանում ենք պահանջված կերպարների ցանկը:
Այնուհետեւ մենք օգտագործում ենք reduce
մեթոդը յուրաքանչյուր կերպարի համար եւ պահում ենք այն ինդեքսը, որով նրանք հայտնվել են, ինչպես նաեւ նրանց անունները:
Հետո counts
օբյեկտը ֆիլտրում ենք ցանկացած կերպարի համար, որի հաշվիչը հավասար է մեկին, եւ այնուհետեւ համեմատում ենք imposters
զանգվածի հետ:
Այնուհետեւ մենք ցուցադրում ենք imposter
ցուցակը եւ ցույց ենք տալիս, թե որտեղ են նրանք գտնվում կերպարի տվյալների մեջ:
Եթե այս գործնական օրինակում մենք չօգտվեինք async/await
-ից, ապա կոդում սխալներ կլինեին:
Ամեն ասինքրոն կոդ չէ, որ այդքան ակնհայտորեն զգուշացնում է խնդրի մասին, ուստի լավ գաղափար է սովորել հասկանալ, թե որտեղ են տեղի ունենում այս իրավիճակները և ինչպես լավագույնս վարվել դրանց հետ:
Ամփոփում
Ահա եւ վերջ:
Երբեմն դժվար է հասկանալ, թե ինչպես կարելի է աշխատել ասինքրոնային կոդի հետ, հատկապես, երբ առաջին անգամ ես գործ ունենում դրա հետ։ Որքան շատ եք հանդիպում ասինքրոնային կոդի, այնքան ավելի լավ եք հաղթահարում նման իրավիճակները:
Հուսով եմ, որ դուք հասկացաք, թե ինչպես է աշխատում async/await
ծրագիրը եւ շատ շուտով կսկսեք ավելի լավ տիրապետել JavaScript
-ին:
Սկզբնաղբյուր՝ How to Use Async/Await in JavaScript Like a Pro
Թարգմանությունը՝ Mariam Abrahamyan