ఉదాహరణలను ఉపయోగించి JavaScriptలో Async/Awaitని చూద్దాం
కథనం యొక్క రచయిత జావాస్క్రిప్ట్లో Async/Await యొక్క ఉదాహరణలను పరిశీలిస్తారు. మొత్తంమీద, అసమకాలిక కోడ్ను వ్రాయడానికి Async/Await అనుకూలమైన మార్గం. ఈ లక్షణం కనిపించడానికి ముందు, అటువంటి కోడ్ కాల్బ్యాక్లు మరియు వాగ్దానాలను ఉపయోగించి వ్రాయబడింది. అసలైన కథనం యొక్క రచయిత వివిధ ఉదాహరణలను విశ్లేషించడం ద్వారా Async/Await యొక్క ప్రయోజనాలను వెల్లడిచారు.
మేము గుర్తు చేస్తున్నాము:Habr పాఠకులందరికీ - Habr ప్రోమో కోడ్ని ఉపయోగించి ఏదైనా Skillbox కోర్సులో నమోదు చేసుకున్నప్పుడు 10 రూబుల్ తగ్గింపు.
Skillbox సిఫార్సు చేస్తోంది: ఎడ్యుకేషనల్ ఆన్లైన్ కోర్సు "జావా డెవలపర్".
బ్యాక్
కాల్బ్యాక్ అనేది కాల్ నిరవధికంగా ఆలస్యం అయ్యే ఫంక్షన్. మునుపు, కోడ్ యొక్క ఆ ప్రాంతాలలో కాల్బ్యాక్లు ఉపయోగించబడ్డాయి, అక్కడ ఫలితం వెంటనే పొందబడదు.
Node.jsలో ఫైల్ను అసమకాలికంగా చదవడానికి ఇక్కడ ఒక ఉదాహరణ ఉంది:
మీరు ఒకేసారి అనేక అసమకాలిక కార్యకలాపాలను చేయవలసి వచ్చినప్పుడు సమస్యలు తలెత్తుతాయి. ఈ దృష్టాంతాన్ని ఊహించుకుందాం: Arfat వినియోగదారు డేటాబేస్కు ఒక అభ్యర్థన చేయబడింది, మీరు దాని profile_img_url ఫీల్డ్ని చదవాలి మరియు someserver.com సర్వర్ నుండి చిత్రాన్ని డౌన్లోడ్ చేసుకోవాలి.
డౌన్లోడ్ చేసిన తర్వాత, మేము చిత్రాన్ని మరొక ఆకృతికి మారుస్తాము, ఉదాహరణకు PNG నుండి JPEGకి. మార్పిడి విజయవంతమైతే, వినియోగదారు ఇమెయిల్కు ఒక లేఖ పంపబడుతుంది. తర్వాత, ఈవెంట్ గురించిన సమాచారం ట్రాన్స్ఫార్మేషన్స్.లాగ్ ఫైల్లో నమోదు చేయబడుతుంది, ఇది తేదీని సూచిస్తుంది.
కోడ్ యొక్క చివరి భాగంలో కాల్బ్యాక్ల అతివ్యాప్తి మరియు పెద్ద సంఖ్యలో })కి శ్రద్ధ చూపడం విలువ. దీనిని కాల్బ్యాక్ హెల్ లేదా పిరమిడ్ ఆఫ్ డూమ్ అంటారు.
ఈ పద్ధతి యొక్క ప్రతికూలతలు స్పష్టంగా ఉన్నాయి:
ఈ కోడ్ చదవడం కష్టం.
లోపాలను నిర్వహించడం కూడా కష్టం, ఇది తరచుగా పేలవమైన కోడ్ నాణ్యతకు దారితీస్తుంది.
ఈ సమస్యను పరిష్కరించడానికి, వాగ్దానాలు JavaScriptకు జోడించబడ్డాయి. కాల్బ్యాక్ల లోతైన గూడును .తర్వాత అనే పదంతో భర్తీ చేయడానికి అవి మిమ్మల్ని అనుమతిస్తాయి.
వాగ్దానాల యొక్క సానుకూల అంశం ఏమిటంటే, అవి కోడ్ను ఎడమ నుండి కుడికి కాకుండా పై నుండి క్రిందికి బాగా చదవగలిగేలా చేయడం. అయితే, వాగ్దానాలకు వాటి సమస్యలు కూడా ఉన్నాయి:
మీరు చాలా .అప్పుడు జోడించాలి.
ప్రయత్నించండి/క్యాచ్కి బదులుగా, అన్ని లోపాలను నిర్వహించడానికి .catch ఉపయోగించబడుతుంది.
ఒక లూప్లో బహుళ వాగ్దానాలతో పని చేయడం ఎల్లప్పుడూ అనుకూలమైనది కాదు; కొన్ని సందర్భాల్లో, అవి కోడ్ను క్లిష్టతరం చేస్తాయి.
చివరి పాయింట్ యొక్క అర్ధాన్ని చూపించే సమస్య ఇక్కడ ఉంది.
యాదృచ్ఛిక వ్యవధిలో (0–n సెకన్లు) 10 నుండి 0 వరకు సంఖ్యల క్రమాన్ని ప్రింట్ చేసే లూప్ మనకు ఉందని అనుకుందాం. వాగ్దానాలను ఉపయోగించి, మీరు ఈ లూప్ను మార్చాలి, తద్వారా సంఖ్యలు 0 నుండి 10 వరకు వరుసగా ముద్రించబడతాయి. కాబట్టి, సున్నాని ప్రింట్ చేయడానికి 6 సెకన్లు మరియు ఒకదాన్ని ప్రింట్ చేయడానికి 2 సెకన్లు తీసుకుంటే, మొదట సున్నాని ప్రింట్ చేయాలి, ఆపై ముద్రణ కోసం కౌంట్డౌన్ ప్రారంభమవుతుంది.
మరియు వాస్తవానికి, ఈ సమస్యను పరిష్కరించడానికి మేము Async/Await లేదా .sortని ఉపయోగించము. ఒక ఉదాహరణ పరిష్కారం ముగింపులో ఉంది.
సమకాలీకరణ విధులు
ES2017 (ES8)లో అసమకాలీకరణ ఫంక్షన్ల జోడింపు వాగ్దానాలతో పని చేసే పనిని సులభతరం చేసింది. అసమకాలిక విధులు వాగ్దానాల "పైన" పనిచేస్తాయని నేను గమనించాను. ఈ విధులు గుణాత్మకంగా భిన్నమైన భావనలను సూచించవు. Async ఫంక్షన్లు వాగ్దానాలను ఉపయోగించే కోడ్కు ప్రత్యామ్నాయంగా ఉద్దేశించబడ్డాయి.
సమకాలీకరణ శైలిలో అసమకాలిక కోడ్తో పనిని నిర్వహించడాన్ని Async/Await సాధ్యం చేస్తుంది.
ఆ విధంగా, వాగ్దానాలను తెలుసుకోవడం వలన Async/Await సూత్రాలను అర్థం చేసుకోవడం సులభం అవుతుంది.
వాక్యనిర్మాణం
సాధారణంగా ఇది రెండు కీలక పదాలను కలిగి ఉంటుంది: సమకాలీకరణ మరియు వేచి ఉండండి. మొదటి పదం ఫంక్షన్ను అసమకాలికంగా మారుస్తుంది. ఇటువంటి విధులు నిరీక్షణను ఉపయోగించడానికి అనుమతిస్తాయి. ఏదైనా ఇతర సందర్భంలో, ఈ ఫంక్షన్ని ఉపయోగించడం వలన లోపం ఏర్పడుతుంది.
// With function declaration
async function myFn() {
// await ...
}
// With arrow function
const myFn = async () => {
// await ...
}
function myFn() {
// await fn(); (Syntax Error since no async)
}
Async అనేది ఫంక్షన్ డిక్లరేషన్ ప్రారంభంలో మరియు బాణం ఫంక్షన్ విషయంలో, “=” గుర్తు మరియు కుండలీకరణాల మధ్య చేర్చబడుతుంది.
ఈ ఫంక్షన్లను ఒక వస్తువులో పద్ధతులుగా ఉంచవచ్చు లేదా క్లాస్ డిక్లరేషన్లో ఉపయోగించవచ్చు.
// As an object's method
const obj = {
async getName() {
return fetch('https://www.example.com');
}
}
// In a class
class Obj {
async getResource() {
return fetch('https://www.example.com');
}
}
NB! క్లాస్ కన్స్ట్రక్టర్లు మరియు గెట్టర్లు/సెట్టర్లు అసమకాలికంగా ఉండరాదని గుర్తుంచుకోవడం విలువ.
సెమాంటిక్స్ మరియు అమలు నియమాలు
Async ఫంక్షన్లు ప్రాథమికంగా ప్రామాణిక JS ఫంక్షన్ల మాదిరిగానే ఉంటాయి, కానీ మినహాయింపులు ఉన్నాయి.
అందువలన, అసమకాలీకరణ విధులు ఎల్లప్పుడూ వాగ్దానాలను అందిస్తాయి:
async function fn() {
return 'hello';
}
fn().then(console.log)
// hello
ప్రత్యేకించి, fn స్ట్రింగ్ హలోను తిరిగి అందిస్తుంది. సరే, ఇది అసమకాలిక ఫంక్షన్ అయినందున, స్ట్రింగ్ విలువ కన్స్ట్రక్టర్ని ఉపయోగించి వాగ్దానంతో చుట్టబడి ఉంటుంది.
Async లేకుండా ప్రత్యామ్నాయ డిజైన్ ఇక్కడ ఉంది:
function fn() {
return Promise.resolve('hello');
}
fn().then(console.log);
// hello
ఈ సందర్భంలో, వాగ్దానం "మాన్యువల్గా" తిరిగి ఇవ్వబడుతుంది. అసమకాలిక ఫంక్షన్ ఎల్లప్పుడూ కొత్త వాగ్దానంతో చుట్టబడి ఉంటుంది.
రిటర్న్ విలువ ఆదిమమైతే, అసమకాలిక ఫంక్షన్ వాగ్దానంలో దాన్ని చుట్టడం ద్వారా విలువను అందిస్తుంది. రిటర్న్ విలువ వాగ్దానం వస్తువు అయితే, దాని రిజల్యూషన్ కొత్త వాగ్దానంలో అందించబడుతుంది.
const p = Promise.resolve('hello')
p instanceof Promise;
// true
Promise.resolve(p) === p;
// true
అసమకాలిక ఫంక్షన్ లోపల లోపం ఉంటే ఏమి జరుగుతుంది?
async function foo() {
throw Error('bar');
}
foo().catch(console.log);
ఇది ప్రాసెస్ చేయబడకపోతే, foo() తిరస్కరణతో వాగ్దానాన్ని అందిస్తుంది. ఈ పరిస్థితిలో, Promise.reject లోపాన్ని కలిగి ఉన్న Promise.resolve బదులుగా అందించబడుతుంది.
Async ఫంక్షన్లు ఎల్లప్పుడూ వాగ్దానాన్ని అందిస్తాయి, దానితో సంబంధం లేకుండా.
ప్రతి నిరీక్షణలో అసమకాలిక విధులు పాజ్ అవుతాయి.
నిరీక్షణ వ్యక్తీకరణలను ప్రభావితం చేస్తుంది. కాబట్టి, వ్యక్తీకరణ వాగ్దానం అయితే, వాగ్దానం నెరవేరే వరకు సమకాలీకరణ ఫంక్షన్ నిలిపివేయబడుతుంది. వ్యక్తీకరణ వాగ్దానం కాకపోతే, అది Promise.resolve ద్వారా వాగ్దానంగా మార్చబడుతుంది మరియు తర్వాత పూర్తి చేయబడుతుంది.
// utility function to cause delay
// and get random value
const delayAndGetRandom = (ms) => {
return new Promise(resolve => setTimeout(
() => {
const val = Math.trunc(Math.random() * 100);
resolve(val);
}, ms
));
};
async function fn() {
const a = await 9;
const b = await delayAndGetRandom(1000);
const c = await 5;
await delayAndGetRandom(1000);
return a + b * c;
}
// Execute fn
fn().then(console.log);
మరియు ఇక్కడ fn ఫంక్షన్ ఎలా పనిచేస్తుందనే వివరణ ఉంది.
కాల్ చేసిన తర్వాత, మొదటి పంక్తి const a = వేచి 9 నుండి మార్చబడుతుంది; in const a = నిరీక్షించు Promise.resolve(9);.
Awaitని ఉపయోగించిన తర్వాత, a దాని విలువను పొందే వరకు ఫంక్షన్ అమలు నిలిపివేయబడుతుంది (ప్రస్తుత పరిస్థితిలో ఇది 9).
delayAndGetRandom(1000) fn ఫంక్షన్ పూర్తి అయ్యే వరకు (1 సెకను తర్వాత) దాని అమలును పాజ్ చేస్తుంది. ఇది fn ఫంక్షన్ను 1 సెకనుకు సమర్థవంతంగా నిలిపివేస్తుంది.
delayAndGetRandom(1000) పరిష్కారం ద్వారా యాదృచ్ఛిక విలువను అందిస్తుంది, అది వేరియబుల్ bకి కేటాయించబడుతుంది.
సరే, వేరియబుల్ సితో ఉన్న కేసు వేరియబుల్ ఎతో సమానంగా ఉంటుంది. ఆ తర్వాత, ప్రతిదీ ఒక సెకను పాటు ఆగిపోతుంది, కానీ ఇప్పుడు ఆలస్యంAndGetRandom(1000) అవసరం లేదు కాబట్టి ఏమీ తిరిగి ఇవ్వదు.
ఫలితంగా, విలువలు a + b * c సూత్రాన్ని ఉపయోగించి లెక్కించబడతాయి. ఫలితం Promise.resolveని ఉపయోగించి వాగ్దానంతో చుట్టబడి, ఫంక్షన్ ద్వారా అందించబడుతుంది.
ఈ పాజ్లు ES6లోని జనరేటర్లను గుర్తుకు తెస్తాయి, కానీ దానికి ఏదో ఉంది మీ కారణాలు.
సమస్యను పరిష్కరించడం
సరే, ఇప్పుడు పైన పేర్కొన్న సమస్యకు పరిష్కారాన్ని చూద్దాం.
FinishMyTask ఫంక్షన్ queryDatabase, sendEmail, logTaskInFile మరియు ఇతర కార్యకలాపాల ఫలితాల కోసం వేచి ఉండటానికి Awaitని ఉపయోగిస్తుంది. మీరు ఈ పరిష్కారాన్ని వాగ్దానాలు ఉపయోగించిన దానితో పోల్చినట్లయితే, సారూప్యతలు స్పష్టంగా కనిపిస్తాయి. అయినప్పటికీ, Async/Await సంస్కరణ అన్ని వాక్యనిర్మాణ సంక్లిష్టతలను చాలా సులభతరం చేస్తుంది. ఈ సందర్భంలో, .then/.catch వంటి పెద్ద సంఖ్యలో కాల్బ్యాక్లు మరియు చైన్లు లేవు.
సంఖ్యల అవుట్పుట్తో ఇక్కడ ఒక పరిష్కారం ఉంది, రెండు ఎంపికలు ఉన్నాయి.
const wait = (i, ms) => new Promise(resolve => setTimeout(() => resolve(i), ms));
// Implementation One (Using for-loop)
const printNumbers = () => new Promise((resolve) => {
let pr = Promise.resolve(0);
for (let i = 1; i <= 10; i += 1) {
pr = pr.then((val) => {
console.log(val);
return wait(i, Math.random() * 1000);
});
}
resolve(pr);
});
// Implementation Two (Using Recursion)
const printNumbersRecursive = () => {
return Promise.resolve(0).then(function processNextPromise(i) {
if (i === 10) {
return undefined;
}
return wait(i, Math.random() * 1000).then((val) => {
console.log(val);
return processNextPromise(i + 1);
});
});
};
మరియు ఇక్కడ async ఫంక్షన్లను ఉపయోగించి ఒక పరిష్కారం ఉంది.
async function printNumbersUsingAsync() {
for (let i = 0; i < 10; i++) {
await wait(i, Math.random() * 1000);
console.log(i);
}
}
ప్రాసెసింగ్ లోపం
నిర్వహించని లోపాలు తిరస్కరించబడిన వాగ్దానంలో చుట్టబడి ఉంటాయి. అయినప్పటికీ, అసమకాలిక విధులు లోపాలను సమకాలీకరించడానికి ప్రయత్నించండి/క్యాచ్ని ఉపయోగించవచ్చు.
async function canRejectOrReturn() {
// wait one second
await new Promise(res => setTimeout(res, 1000));
// Reject with ~50% probability
if (Math.random() > 0.5) {
throw new Error('Sorry, number too big.')
}
return 'perfect number';
}
canRejectOrReturn() అనేది అసమకాలిక ఫంక్షన్, ఇది విజయవంతం అవుతుంది (“పరిపూర్ణ సంఖ్య”) లేదా లోపంతో విఫలమవుతుంది (“క్షమించండి, సంఖ్య చాలా పెద్దది”).
పైన ఉన్న ఉదాహరణ canRejectOrReturn అమలు చేయాలని ఆశించినందున, దాని స్వంత వైఫల్యం క్యాచ్ బ్లాక్ యొక్క అమలుకు దారి తీస్తుంది. ఫలితంగా, ఫంక్షన్ ఫూ నిర్వచించబడని (ట్రై బ్లాక్లో ఏదీ తిరిగి రానప్పుడు) లేదా క్యాచ్ చేయబడిన ఎర్రర్తో ముగుస్తుంది. ఫలితంగా, ఈ ఫంక్షన్ విఫలం కాదు ఎందుకంటే ట్రై/క్యాచ్ ఫూ ఫంక్షన్ని నిర్వహిస్తుంది.
ఉదాహరణలో, canRejectOrReturn foo నుండి తిరిగి ఇవ్వబడిందనే దానిపై దృష్టి పెట్టడం విలువ. ఫూ ఈ సందర్భంలో ఖచ్చితమైన సంఖ్యతో ముగుస్తుంది లేదా లోపాన్ని తిరిగి ఇస్తుంది ("క్షమించండి, సంఖ్య చాలా పెద్దది"). క్యాచ్ బ్లాక్ ఎప్పటికీ అమలు చేయబడదు.
సమస్య ఏమిటంటే canRejectOrReturn నుండి ఆమోదించబడిన వాగ్దానాన్ని foo తిరిగి ఇస్తుంది. కాబట్టి ఫూకు పరిష్కారం canRejectOrReturnకు పరిష్కారం అవుతుంది. ఈ సందర్భంలో, కోడ్ రెండు పంక్తులను మాత్రమే కలిగి ఉంటుంది:
పై కోడ్లో, ఫూ ఒక ఖచ్చితమైన సంఖ్య మరియు క్యాచ్ అయిన ఎర్రర్ రెండింటితో విజయవంతంగా నిష్క్రమిస్తుంది. ఇక్కడ తిరస్కారాలు ఉండవు. కానీ foo undefined తో కాకుండా canRejectOrReturnతో తిరిగి వస్తుంది. రిటర్న్ వెయిట్ canRejectOrReturn() లైన్ను తీసివేయడం ద్వారా దీన్ని నిర్ధారించుకుందాం:
మీరు గమనిస్తే, కోడ్లో నిరీక్షణ లేదా రిటర్న్ లేదు. అందువల్ల foo ఎల్లప్పుడూ 1 సెకను ఆలస్యం లేకుండా నిర్వచించబడకుండా నిష్క్రమిస్తుంది. కానీ ఇచ్చిన హామీ నెరవేరుతుంది. ఇది ఎర్రర్ లేదా తిరస్కరణను విసిరితే, UnhandledPromiseRejectionWarning అని పిలుస్తారు.
కాల్బ్యాక్లలో సమకాలీకరణ విధులు
Async ఫంక్షన్లు చాలా తరచుగా .map లేదా .filterలో కాల్బ్యాక్లుగా ఉపయోగించబడతాయి. FetchPublicReposCount(యూజర్నేమ్) ఫంక్షన్ ఒక ఉదాహరణ, ఇది GitHubలో ఓపెన్ రిపోజిటరీల సంఖ్యను అందిస్తుంది. మనకు అవసరమైన కొలమానాలు ముగ్గురు వినియోగదారులు ఉన్నారని అనుకుందాం. ఈ టాస్క్ కోసం కోడ్ ఇక్కడ ఉంది:
.మ్యాప్ కాల్బ్యాక్లో నిరీక్షించడంపై దృష్టి పెట్టడం విలువైనదే. ఇక్కడ గణనలు అనేది వాగ్దానాల శ్రేణి, మరియు .మ్యాప్ అనేది ప్రతి పేర్కొన్న వినియోగదారుకు అనామక కాల్బ్యాక్.
నిరీక్షణ యొక్క అధిక స్థిరమైన ఉపయోగం
ఈ కోడ్ని ఉదాహరణగా తీసుకుందాం:
async function fetchAllCounts(users) {
const counts = [];
for (let i = 0; i < users.length; i++) {
const username = users[i];
const count = await fetchPublicReposCount(username);
counts.push(count);
}
return counts;
}
ఇక్కడ రెపో నంబర్ కౌంట్ వేరియబుల్లో ఉంచబడుతుంది, ఆపై ఈ సంఖ్య గణనల శ్రేణికి జోడించబడుతుంది. కోడ్తో సమస్య ఏమిటంటే, మొదటి వినియోగదారు డేటా సర్వర్ నుండి వచ్చే వరకు, తదుపరి వినియోగదారులందరూ స్టాండ్బై మోడ్లో ఉంటారు. అందువలన, ఒక సమయంలో ఒక వినియోగదారు మాత్రమే ప్రాసెస్ చేయబడతారు.
ఉదాహరణకు, ఒక వినియోగదారుని ప్రాసెస్ చేయడానికి సుమారు 300 ms తీసుకుంటే, వినియోగదారులందరికీ ఇది ఇప్పటికే రెండవది; సరళంగా గడిపిన సమయం వినియోగదారుల సంఖ్యపై ఆధారపడి ఉంటుంది. కానీ రెపో సంఖ్యను పొందడం ఒకదానిపై ఒకటి ఆధారపడి ఉండదు కాబట్టి, ప్రక్రియలు సమాంతరంగా ఉంటాయి. దీనికి .map మరియు Promise.allతో పని చేయడం అవసరం:
Promise.all ఇన్పుట్గా వాగ్దానాల శ్రేణిని అందుకుంటుంది మరియు వాగ్దానాన్ని అందిస్తుంది. శ్రేణిలోని అన్ని వాగ్దానాలు పూర్తయిన తర్వాత లేదా మొదటి తిరస్కరణలో రెండోది పూర్తవుతుంది. అవన్నీ ఒకే సమయంలో ప్రారంభం కాకపోవచ్చు - ఏకకాల ప్రారంభాన్ని నిర్ధారించడానికి, మీరు p-మ్యాప్ని ఉపయోగించవచ్చు.
తీర్మానం
అసమకాలీకరణ విధులు అభివృద్ధికి చాలా ముఖ్యమైనవిగా మారుతున్నాయి. సరే, అసమకాలిక ఫంక్షన్ల అనుకూల ఉపయోగం కోసం ఇది ఉపయోగించడం విలువైనది అసమకాలిక ఇటరేటర్లు. జావాస్క్రిప్ట్ డెవలపర్కు ఇందులో బాగా ప్రావీణ్యం ఉండాలి.