Callback függvény, promise objektum, aszinkron JavaScript, párhuzamos programozás.


Definíció szerint a callback függvények olyan függvények, melyek argumentumába egy másik függvény kerül, melyet ezután a külső függvényben meghívunk valamilyen művelet végrehajtására. Megkülönböztetjük egymástól a szinkron és aszinkron callback függvényeket.

Szinkronnak nevezzük az olyan utasításokat, amelyek arra késztetik a számítógépet, hogy ameddig az egyik utasítás le nem fut, addig más utasítások ne tudjanak lefutni (feltartva a még nem végrehajtott utasítások végrehajtását).

Aszinkronnak nevezzük az olyan utasításokat, amelyek arra késztetik a számítógépet, hogy ameddig az aszinkron utasítás le nem fut, addig más utasításokat is tudjon futtatni párhuzamosan. Párhuzamos programozás esetén időigényes utasítássorozat futtatása esetén nem kell szükségszerűen egyszerre egy utasítást futattni, feltéve ha adottak a megfelelő körülmények.

A szinkron és aszinkron programozás összehasonlítása

A Promise egy olyan objektum, mely egy aszinkron művelet egy lehetséges végkimenetelét reprezentálja, melyek szintaxisa a következő:


var peldany = new Promise((teljesit, visszautasit) => {
  // végrehajtó függvény törzse
});

A Promise argumentuma egy úgynevezett végrehajtó függvényt vár. Amikor a Promise objektumot lepéldányosítjuk, a végrehajtó függvény automatikusan lefut. A függvény törzsébe foglaljuk azt a kódot, melyet végre szeretnénk hajtani. A végrehajtó függvény argumentuma két callback-et (teljesit és visszautasit), melyet maga a JavaScript szolgáltat. Az elnevezésük lényegtelen, a funkcionalitásuk ugyanaz marad [18]. A Promise-nak három lehetséges állapota lehet:

• Teljesítve (teljesitd) - az utasítás végrehajtása sikeresen befejeződött.

• Visszautasítva (visszautasited) - az utasítás végrehajtása sikertelenül fejeződött be.

• Folyamatban (Pending) - az utasítás végrehajtása folyamatban van.

Amikor a végrehajtó függvény megkapja az eredményt, akkor a két callback közül az egyik meghívódik. Mindegyik callback olyan értékkel tér vissza, amit beállítunk neki. Mint ahogy korábban említettük, a késleltetés aszinkron módon történik, a program többi részének futását nem fogja feltartani, miközben zajlik a késleltetés. Működését egy esemény-hurokként lehet egyszerűen szemléltetni. Az 1 másodperces késleltetés egész egyszerűen azért nem garantált, mert annyi utasítás is lefuthat előtte, aminek össz futási ideje meghaladhatja ezt a beállított késleltetési időt.

const igeretvisszater = () => {
  return new Promise((teljesit, visszautasit) => {
    setTimeout(( ) => {teljesit('Az ígéret teljesült!')}, 1000);
  })
};

const prom = returnPromiseFunction();

A promise objektum rendelkezik az úgynevezett then() metódussal, melyben definiálhatjuk, hogy milyen utasításokat szeretnénk lefutattni, miután beállt a promise az egyik lehetséges állapotba. Érdemes az összes lehetséges szituációt megfelelően előre lekezelni, különben a hibakeresés folyamatát saját magunk nehezítjük meg. Vegyük észre, hogy a then() mindig promise-al tér vissza.

const az_igeret = new Promise((teljesit, visszautasit) => {
  teljesit('Szép_szó!');
});

const sikerkezeles = (teljesitettertek) => {
  console.log(teljesitettertek);
};

igeret.then(sikerkezeles); 
// Szép_szó!

A fenti példakód szerint az igeret egy promise, ami a Szép_szó teljesítéssel tér vissza ha meghívjuk. Továbbá definiáltuk a sikerkezeles függvényt, ami kiírja konzolra az argumentumába átadott értéket. Hívjuk az_igeret-et, majd hajtsuk végre a then() metódust és az argumentumában adjuk át a sikerkezeles-t. Mivel az_igeret automatikusan teljesül, a sikerkezeles meghívódik és kiírja a teljesített értéket a konzolra. Általános esetben nem elégséges a teljesített állapotot lekezelni, a másikkal is kell foglalkozni, hisz nem ismerhetjük minden egyes folyamat végkimenetelét. A lenti példa működése hasonlít a fenti példakódra azzal a különbséggel, hogy a then() metódus immáron egy helyett két függvény paramétert vár. A másik függvény paraméternek átadjuk a visszautasitott állapot lekezeléséért felelős függvényt. A példát kibővítve a két állapot egy sorban is lekezelhető mégpedig úgy, hogy a fenti két then() metódust láncba összekötjuk. A gyakorlatban a hiba kezelését külön választják a jobb átláthatóság érdekében a catch() metódus meghívásával. Működése szerint egy függvény paraméterrel rendelkezik (onvisszautasit). Amennyiben a promise a visszautasítva állapotba kerül, a catch() meghívódik és lefutnak a belefoglalt utasítások.

let igeret = new Promise((teljesit, visszautasit) => {
  let szam = Math.random();
  if (szam < .5 ){
    teljesit('A szám kisebb, mint .5!');
  } else {
    visszautasit('A szám nagyobb, mint .5!');
  }
});

const sikerkezel = (teljesitettertek) => {
  console.log(teljesitettertek);
};

const visszautasitottkezel = (visszautasitottertek) => {
  console.log(visszautasitottertek);
};

igeret.then(sikerkezel, visszautasitottkezel);

// hibakezelés catch() metódussal

igeret.then((teljesitettertek) => {
	console.log(teljesitettertek);
}).catch((visszautasitottertek) => {
    console.log(visszautasitottertek);
  });

Az aszinkron programozás egyik gyakran használt mintája az utasítások meghatározott sorrendben történő futtatása. Tegyük fel, hogy egy adatbázis felé intézünk egy kérést és a megérkező adatokat felhasználnánk további kérésekhez és így tovább. Vegyük észre, hogy mindegyik kérés egy-egy promise, amiket úgy kell összekötni, hogy egymást bevárva egyszerre csak egy fusson le (ahogy a soros programozásnál megszokhattuk).

elso_igeret().then((elsoteljesitettertek) => {
	return masodik_igeret(elsoteljesitettertek);
}).then((masodikteljesitettertek) => {
	console.log(masodikteljesitettertek);
});

Működése szempontjából először meghívódik az elso_igeret, ami visszatér egy promise-al. A teljesített állapotot then() metódus meghívásával lekezelődik. Ebben az ágban visszatérünk egy promise-al, ennek eredményeképpen meghívódik a masodik_igeret. Az erre vonatkozó teljesített állapot egy másik then() metódus meghívódással lekezelődik. Végül ebben az ágban kiíratódik a masodikteljesitettertek paraméter értéke. Ahhoz, hogy megfelelően összekössük a két promise-t, visza kellet térni a masodik_igeret(elsoteljesitettertek) promise-al. Ezzel azt értük el, hogy az első then() visszatérési értéke legyen a második promise.

Mi van abban az esetben, ha ugyanúgy több promise-t kell kezelnünk, viszont a végrehajtás sorrendje nem számít? Tegyük fel, hogy a lakásunkat ki kell takarítani, a ruháinkat meg kell szárítani, és el kell mosni az edényünket mosogatógéppel. Mindegyik házimunkát el kell végezni, viszont a sorrend nem számít. Vegyük észre, hogy az összes munka párhuzamosan fut. A probléma megoldására kitalálták a Promise.all()-t, mely paraméterként tömböt vár, elemei pedig egy-egy promise. Amennyiben bármelyik promise visszautasított állapotba áll be, a Promise.all() is ugyanúgy visszautasított állapotbaáll be.

var tomb = [elsoigeret(), masodikigeret(), harmadikigeret()];
var osszesigeret = Promise.all(tomb);

osszesigeret.then((tombertekek) => {
	console.log(tombertekek);
}).catch((visszautasitottertek) => {
	console.log(visszautasitottertek);
  });

A fenti példakód működése a következő: definiálunk egy tomb nevű tömböt, melynek eleme három promise. Definiálunk egy osszesigeret változót és értékül adjuk a Promise.all(tomb)-t. Az osszesigeretet meghívása után ha nem következik be hiba, mind a három promise lefut párhuzamosan a then() ágban. Hiba esetén a catch() ág hívódik meg és leállítja az összes futó utasítást (értékek kiírása).

A JavaScript ES8 bevezetésekor a párhuzamos programozás szintaxisa némileg megváltozottt, már másképp is tudjuk kezelni a promise-kat. Ezek közül megmutatjuk mit takarnak a async és await kulcsszavak. Az async kulcsszót alkalmazva a soros programozás során megismert függvény deklarációban olyan függvényt tudunk létrehozni, mely aszinkron módon képes lefutni. Egy ilyen aszinkron függvény három dologgal térhet vissza:

• Amennyiben nincs beállítva visszatérési érték, automatikusan visszatér egy promise-al teljesítve állapotban undefined értékkel

• Amennyiben nem promise van beállítva visszatérési értéknek, automatikusan visszatér egy promise-al teljesítve állapotban ugyanazzal az értékkel

• Amennyiben promise van beállítva visszatérési értéknek, azzal fog visszatérni

A lenti példában definiáltunk egy igeret nevű konstants, melyhez egy aszinkron függvényt rendeltünk, melynek visszatérési értéke 5. Ezt a függvényt meghívva visszatér egy promise-al teljesítve állapotban és kiírja azt, hogy 5.

const igeret = async () => {
  return 5;
};

igeret().then(teljesitettertek => {
	console.log(teljesitettertek);  
	//  5
});

Az await, mint operátor kizárólag az aszinkron függvényen belül van értelmezve, ami visszatér a promise teljesítve állapothoz rendelt értékkel. Mivel a folyamatban állapotból a teljesítve állapotba való beállás közt eltelt idő előre nem meghatározható, az await feltartóztatja/megállítja az aszinkron függvény végrehajtódását, amíg a promise állapota be nem áll a folyamatban-tól különböző állapotba. A legtöbb esetben promise-al akkor találkozunk, amikor egy függvény visszatér vele. A bejelentes nevű aszinkron függvényen belül az await kulcsszót használva feltartóztatjuk a valami() végrehajtását amíg a promise be nem áll teljesítve állapotba. A visszatért értéket átadjuk az akarmi változónak és kiíratjuk a kapott értéket. Az await kulcsszó nélkül a bejelentes függvény azt írja ki, hogy Promise { <pending> }, ami azt jelenti, hogy a promise még nem állt a teljesített állapotba.


const ezt_kell_felhasznalni = () => {
  return new Promise((teljesit, visszautasit) => {
  setTimeout(() => {
    teljesit('Végre értem az aszinkron függvények használatát!');
  }, 1000)
})
};

const bejelentes = async () => {
  let akarmi = await ezt_kell_felhasznalni();
  console.log(akarmi)  
  // Végre értem az aszinkron függvények használatát!;
}

bejelentes();

// a feladat natív promise-al való megoldása

const masikbejelentes = () => {
  valami().then((akarmi) => {
	  console.log(akarmi);  
	  // Végre értem az aszinkron függvények használatát!
  })
}

masikbejelentes();

Az async és await kulcsszavak igazi ereje akkor mutatkozik meg az aszinkron programozásban, amikor több, egymással összefüggő aszinkron utasításokat kell kezelni. A lenti példában röviden szót ejtünk erre az eshetőségre is. Az aszinkronfuggveny nevű aszinkron függvényen belül az await kulcsszót használva feltartóztatjuk az elso végrehajtását amíg a promise be nem áll teljesítve állapotba. A masodik-kal is ugyanezt kell eljátszani azzal a különbséggel, hogy van egy bemeneti paramétere, ahova az első promise teljesitve állapotához rendelt érték tartozik. Az aszinkronfuggveny meghívását követően 1 másodperccel kiíródik az elsoertek értéke, majd megint 1 másodpercre rá kiíródik a masodikertek értéke.

const elso = () => {
  return new Promise((teljesit, visszautasit) => {
  setTimeout(() => {
	const elsoertek = 'Első';
    teljesit(elsoertek);
  }, 1000);
})
}

const masodik = (elsoertek) => {
  return new Promise((teljesit, visszautasit) => {
  setTimeout(() => {
	const masodikertek = elsoertek.concat(' Második ');
    teljesit(masodikertek);
  }, 1000);
})
}

const aszinkronfuggveny = async () => {
 let elsoertek = await elso();
 console.log(elsoertek);  
 // Első
 let masodikertek = await masodik(elsoertek);
 console.log(masodikertek);  
 //  Első Második
};

aszinkronfuggveny();

Ahogy a fenti példában is láthatjuk, amikor több aszinkron utasítást hajtunk végre egymás után, hiba esetén nem tudjuk, hogy konkrétan hol lépett fel a hiba. A lenti példában megmutatjuk, hogy az ES8 szintaxissal a hibakezelést miképp implementálhatjuk az úgynevezett try és catch kulcsszavak használatával. Ennek a módszernek az egyik előnye, hogy nemcsak aszinkron, hanem szinkron utasításokból eredendő hibákat is le lehet kezelni.

let eredmeny = () => {
 let szam = Math.random();
 if (szam > .5 ){
   return false;
 } else {
   return true;
 }
};

let fuggveny = () => {
 return new Promise((teljesit, visszautasit) => {
   setTimeout(()=>{
     let igaz_vagy_nem = eredmeny();
     if(igaz_vagy_nem){
       teljesit('A szám nagyobb, mint .5!');
     } else {
       visszautasit('A szám kisebb, mint .5!');
     }
   }, 1000);
 })
};

const aszinkronfuggveny_hibakezelessel = async () => {
 try {
   let teljesult = await fuggveny();
   console.log(teljesult);  
   //  'A szám nagyobb, mint .5!'
 }
 catch(error){
   console.log(error);  
   //  'A szám kisebb, mint .5!'
 }
}

aszinkronfuggveny_hibakezelessel();

Mi van abban az esetben, ha ugyanúgy több promise-t kell kezelnünk, viszont a végrehajtás sorrendje nem számít? A probléma megoldására kitalálták szintén alkalmazható a Promise.all()-t annyi különbséggel, hogy előtte az await kulcsszót kell használni. A lenti példában a Promise.all() argumentumában definiálunk egy tömböt, melynek elemei legyenek a deklarált függvények. Ez visszatér egy promise-al és az állapota akkor vált át teljesítetté, amikor az argumentumában definiált összes függvény promise-a teljesített állapotba nem áll át. A visszakapott értékek a tomb nevű tömbben mentődnek el. Végül egy for ciklussal kiíratjuk a tomb értékeit. Amennyiben bármelyik promise visszautasított állapotba kerül, a Promise.all() azonnal visszautasított állapotba kerül és a hibánál fellépet függvény hibakezelő ágában implementált értékkel tér vissza.

const elso = () => {
  return new Promise((teljesit, visszautasit) => {
  setTimeout(() => {
    teljesit('Első');
  }, 1000);
})
}

const masodik = (elsoertek) => {
  return new Promise((teljesit, visszautasit) => {
  setTimeout(() => {
    teljesit('Második');
  }, 1000);
})
}

const aszinkronfuggveny = async () => {
  const tomb = await Promise.all([elso(), masodik()]);
  for (let i = 0; i<tomb.length; i++){
    console.log(tomb[i]);  
	//  Első
	//  Második
  }
}

aszinkronfuggveny();

Irodalomjegyzék

[1]
Vue.js core team. Vue.js: The Progressive JavaScript Framework. https://vuejs.org, Legutóbb megtekintve: 2019. április 22.
[2]
Vue.js core team. API, Global Config, directive. https://vuex.vuejs.org/vuex.png, Legutóbb megtekintve: 2019. április 22.
[3]
Vue.js core team. API, Global Config, computed. https://vuejs.org/v2/api/#computed, Legutóbb megtekintve: 2019. április 22.
[4]
Vue.js core team. API, Global Config, components. https://vuejs.org/v2/api/#components, Legutóbb megtekintve: 2019. április 22.
[5]
Vue.js core team. Vue-CLI. https://cli.vuejs.org/guide/creating-a-project.html#vue-create, Legutóbb megtekintve: 2019. április 22.
[6]
Vue.js core team. Vue-router. https://router.vuejs.org/guide/#html, Legutóbb megtekintve: 2019. április 22.
[7]
Vue.js core team. Render Functions and JSX. https://vuejs.org/v2/guide/render-function.html, Legutóbb megtekintve: 2019. április 22.
[8]
w3schools core team. JavaScript RegExp Reference. https://www.w3schools.com/jsref/jsref_obj_regexp.asp, Legutóbb megtekintve: 2019. április 22.
[9]
Google Chrome DevTools core team. Chrome DevTools. https://developers.google.com/web/tools/chrome-devtools, Legutóbb megtekintve: 2019. április 22.
[10]
Node.js core team. Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine. https://nodejs.org/en/, Legutóbb megtekintve: 2019. április 22.
[11]
NPMJS core team. NPMJS. https://www.npmjs.com, Legutóbb megtekintve: 2019. április 22.
[12]
ExpressJS core team. Express Fast, unopinionated, minimalist web framework for Node.js. https://expressjs.com, Legutóbb megtekintve: 2019. április 22.
[13]
EJS core team. EJS: Embedded JavaScript templating. https://ejs.co, Legutóbb megtekintve: 2019. április 22.
[14]
Olga Filipova. Learning Vue.js 2. Packt Publishing Ltd., Livery Place, 35 Livery Street, Birmingham, B3 2PB, UK, 3, 2016.
[15]
E.F. Codd. A relational Model of Data for Large Shared Data Banks. Communications of the ACM, 13 (6) 1970.
[16]
Papp Edit. Adatbáziskezelés. Booklands 2000 Könyvkiadó Kft., 5600 Békéscsaba, Dr. Becsey Oszkár u. 42., 1, 2004.
[17]
Nemzeti Szakképzési és Felnőttképzési hivatal. Szoftverfejlesztő tanfolyam. https://www.nive.hu, Legutóbb megtekintve: 2019. április 22.
[18]
JavaScript.info core team. JavaScript.info. https://javascript.info/promise-basics, Legutóbb megtekintve: 2019. április 22.
[19]
Craig Buckler. Sitepoint (JavaScript) - Understanding ES6 Modules. https://www.sitepoint.com/understanding-es6-modules/, Legutóbb megtekintve: 2019. április 22.