Kezdőlap » Promise – aszinkron JavaScript – 2. rész

Promise – aszinkron JavaScript – 2. rész

Mi az a Promise?

A Promise egy olyan objektum, ami azt figyeli, hogy egy bizonyos esemény már bekövetkezett-e vagy sem. Meg is határozza, hogy mi történjen azután, ha az esemény bekövetkezett. Természetesen aszinkron eseményekről van szó.

Egyszerűbben: azt mondom, hogy adj nekem vissza adatokat a szerverről a háttérben és a Promise megígéri nekem, hogy okés, megkapom az adatokat, így kezelni tudom ezeket az adatokat a jövőben.

“Kiváltó oka”, vagy “előzménye” a Promise-ok megjelenésének talán az a CallBack Hell-nek elnevezett jelenség, amit az előző posztban is bemutattam.

Promise állapotok

Az ígéret szép szó, ha megtartják úgy jó. Egy Promise-nak különböző állapotai lehetnek.

  • Mielőtt az esemény bekövetkezik az az állapot a “pending“.
  • Majd miután az esemény bekövetkezett az állapot “resolved“.
  • Ha a Promise sikeresen véget ért, ami azt jelenti, hogy az adat a rendelkezésünkre áll, akkor az állapot neve “fulfilled“.
  • Bármilyen hiba esetén pedig “rejected” állapota van a Promise-nak.

A fulfilled és rejected állapotokat képesek vagyunk kezelni a kódon belül, majd meglátod hogyan.

Promise létrehozása

A hozott példában a CallBack hell posztban kifejtett kódból indulunk ki. Azt fogjuk átírni Promise-os kódra.

Mivel a Promise egy objektum, a new kulcsszóval hozzuk létre:

new Promise()

Paraméterként átadunk neki egy callBack-et, amit executornak nevezünk, ami egy olyan függvény, ami azonnal meghívódik, miután a Promise létrejön. legyen most ez egyelőre egy üres arrow function:

new Promise(() => {
});

Az egészet hozzárendelhetjük egy változóhoz is:

const azonositokLekerese = new Promise(() => {
});

Elsőként azt valósítjuk meg, ami a callBack hell-es példában az első setTimeout volt, amikor a recept azonosítókat elkértük a weboldaltól.

Az executor függvény

A Promise paraméterében átadott callback tehát az ún. executor függvény. Ennek két paramétere van. Az egyik a resolve, a másik a reject:

const azonositokLekerese = new Promise((resolve, reject) => {
});

Ez gyakorlatilag két állapotot jelent. És ezzel a két állapottal informálja az executor függvény a Promise-t, hogy a kezelt esemény (a kérés) sikeres volt vagy sikertelen.

Ha sikeres volt, akkor a resolve függvényt hívjuk meg, ha sikertelen, akkor pediga  reject függvényt.

A Promise belsejében aszinkron dolgok történnek, ezért most egy aszinkron hívást imitálunk. Ahogy azt korábban is tettem, most is setTimeout függvénnyel tudjuk imitálni ezt az aszinkron működést:

const azonositokLekerese = new Promise((resolve, reject) => {
	setTimeout(() => {
	}, 2000);
});

Válasz a szervertől: a then és a resolve függvény

Az áhított szitu, hogy miután az időzítő elüti a két másodpercet, az adat megérkezzen a szervertől, vagyis azonosítókat kapjunk vissza. Itt az idő tehát a resolve függvény használatára. Emlékeztetnék mindenkit, hogy a resolve akkor kerül hívásra, ha sikerül a lekérdezés, azaz a Promise  állapota az, hogy fulfilled. A resolve függvénynek van egy paramétere, ami a Promise-nak az eredménye. Esetünkben a Promise eredménye, az azonosítók tömbje, amit most “beégetünk” a kódba, feltételezve, hogy most ezt kaptuk vissza a szervertől:

const azonositokLekerese = new Promise((resolve, reject) => {
	setTimeout(() => {
		resolve([676, 102, 34, 1089, 321]);
	}, 2000);
});

Minden Promise-nak van két metódusa, amiket a Promise objektumtól örököl. Az egyik a then, a másik a catch. A then lehetővé teszi, hogy kezeljük azt az eseményt (esetet), amikor a Promise fulfilled, ami azt jelenti, hogy van valami kézzel fogható eredményünk. Az “eseménykezelő” egy callback. Funkcióját tekintve pedig ez a callback, például indíthat egy újabb lekérdezést. Mutatom, hogyan kell használni:

azonositokLekerese.then(() => {
});

A then-ben átadott callback-nek mindig van egy paramétere. A paraméter pedig nem más, mint az eredménye a sikeres Promise-nak. Ez esetünkben egy tömb, ami az azonosítókat tartalmazza:

azonositokLekerese.then((azonositok) => {
	console.log(azonositok);
});

Nézzük meg a böngészőben az eredményt. Azt kell látnunk, hogy 2 másodperc után megjelenik a konzolban a tömb.

Hibakezelés: catch és rejected

Ahogy említettem, a Promise-ok másik metódusa a catch. A catch metódussal pedig azt az esetet tudjuk kezelni, amikor a Promise-nak nem lesz eredménye valami hiba következtében, vagyis a rejected fog teljesülni. A catch paraméterében megadott callback-nek is van egy paramétere, ami a hibát fogja tartalmazni.

Még mielőtt ezt megmutatom, még egy fontos dolog: a metódusokat össze is lehet láncolni, amivel könnyebb érzékeltetni, hogy ugyanahhoz a Promise-hoz tartoznak. Mutatom, hogy a kódban hogyan néz ez ki, és egyúttal akkor jöhet a catch is:

azonositokLekerese
.then((azonositok) => {
	console.log(azonositok);
})
.catch(hiba => {
	console.log(hiba);
)};

Próbáljuk ki most azt, hogy a Promise-t szándékosan hibára futtatjuk, hogy működik-e a catch. Ehhez a resolve hívást kell átírni:

reject("Valami hiba");

Próba a böngészőben és azt kell látni, hogy ez is működik. Most azonban kikommentezve ott hagyom a reject-et és a resolve esettel foglalkozunk csak:

setTimeout(() => {
	resolve([676, 102, 34, 1089, 321]);
	//reject("Valami hiba");
}, 2000);

A következő aszinkron művelet, amit hasonlóképpen Promise-al csinálunk meg az egy konkrét recept adatainak lekérése. Kell ehhez egy olyan függvény, ami megkapja a recept id-ját és ez alapján visszaadja a recept adatait:

const recept = id => {
};

A függvény visszatérési értéke egy új Promise-lesz az előbbi mintájára:

const recept = id => {
	return new Promise((resolve, reject) => {
	});
};

Ami a promise belsejében történni fog az, amit a callBack hell esetében is csináltunk:

const receptLekeres = receptID => {
	return new Promise((resolve, reject) => {
		setTimeout((id) => {
			const recept = {
				cim: 'Gulyás leves',
				kategoria: 'Levesek'
			};
			resolve(`${id}: ${recept.cim}`);
		}, 1500, receptID);
	});
};

Láncolt Promise-ok

Na most felmerül a kérdés, hogy ennek a recept függvény által visszaadott Promise-t hol kezeljük? Tehát hol történik a then és a catch hívása?

Itt mutatkozik meg a Promise-ok használatának szépsége. Ugyanis a Promise-ok egymásba ágyazhatók, összeláncolhatók. Van nekünk ez a kód:

azonositokLekerese
.then((azonositok) => {
	console.log(azonositok);
})
.catch(hiba => {
	console.log(hiba);
});

Az első Promise then metódusán belül hívjuk meg a recept függvényt, aminek átadjuk az egyik tömb elemet, ami valamelyik receptnek az azonosítója:

azonositokLekerese
.then((azonositok) => {
	console.log(azonositok);
	return receptLekeres(azonositok[2]);
})
.catch(hiba => {
	console.log(hiba);
});

Fontos, hogy return után adjuk meg a függvény hívását, mert a Promise-t vissza kell adnunk, hogy le tudjuk kezelni.

Na és most jön az, amit a Promise-ok egymásba láncolásánál mondtam. Az első then után adjuk meg a visszaadott Promise, then metódusának hívását:

azonositokLekerese
.then((azonositok) => {
	console.log(azonositok);
	return receptLekeres(azonositok[2]);
})
.then(recept => {
	console.log(recept)
})
.catch(hiba => {
	console.log(hiba);
});

Ennek a metódusnak is callback a paramétere. Ennek a callbacknek a visszaadott recept az egyetlen paramétere és semmi mást nem csinálunk vele, mint, hogy a callBack törzsében kiíratjuk a konzolba.

Ha most megnézi mindenki a böngészőben, akkor azt kell tapasztalni, hogy először 2mp eltelte után megjelenik a konzolban a recept azonosítókat tartalmazó tömb, majd 1,5mp után a recept is. 

Ha most összehasonlítjuk az eddigi kódot a callback hell kódjával, már most látjuk, hogy a Promise-os megoldás mennyivel struktúráltabb, olvashatóbb. Minden függvény külön van és a Promise-ok eredményének kezelése is szépen áttekinthető.

És végül nem maradt más hátra, mint a kategória szerinti receptek lekérdezése, ami szintén egy Promise-al tér vissza. Illetve ennek a Promise-nak a kezelése.

Konklúzió és hova tovább?

Íme a végső, teljes kód tehát:

const azonositokLekerese = new Promise((resolve, reject) => {
	setTimeout(() => {
		resolve([676, 102, 34, 1089, 321]);
		//reject("Valami hiba");
	}, 2000);
});

const receptLekeres = receptID => {
	return new Promise((resolve, reject) => {
		setTimeout((id) => {
			const recept = {
				cim: 'Gulyás leves',
				kategoria: 'Levesek'
			};
			resolve(`${id}: ${recept.cim}`);
		}, 1500, receptID);
	});
};

const kategoriaLekeres = kategoria => {
	return new Promise ((resolve, reject) => {
		setTimeout(kat => {
				const levesek = [
					{ cim: 'Nyírségi gombócleves', kategoria: 'Levesek' },
					{ cim: 'Borsóleves', kategoria: 'Levesek' },
				];
				resolve(levesek);
			}, 1500, kategoria);
		});
};

azonositokLekerese
.then((azonositok) => {
	console.log(azonositok);
	return receptLekeres(azonositok[2]);
})
.then(recept => {
	console.log(recept);
	return kategoriaLekeres(recept.kategoria);
})
.then(kategoria => {
	console.log(kategoria);
})
.catch(hiba => {
	console.log(hiba);
});

Egy tök szép, jól tagolt kódunk van. Fényévekre van a callBack hell posztban látott kódtól. És a jó hír, hogy lehet ezt még ennél is jobban csinálni. A következő bejegyzésben (Vigyázat, Spoiler!!! :)) megnézzük az async-await használatát.