ugrás a tartalomhoz

Asynchronous Javascript – the tale of Harry

Joó Ádám · 2013. Feb. 4. (H), 04.57
A Node.js JavaScript feloldhatatlan problémái
 
1

Aszinkronitás

Hidvégi Gábor · 2013. Feb. 4. (H), 11.19
Ezzel az egész node.js "aszinkron" - "nem blokkoló" működésével kapcsolatban szerintem egy probléma van: kit érdekel? Milyen többletinformációt hordoz a programozó számára ez az egész? Ha pedig előnnyel nem jár, akkor miért így tervezték meg a nyelvet (a szemantikát)? Ha úgy vesszük, semmi sem blokkol, mert IO esetén az operációs rendszer a többi programot ugyanúgy futtatja tovább.
2

És mi van, ha az összes szál

BlaZe · 2013. Feb. 4. (H), 11.31
És mi van, ha az összes szál IO-ra vár? :)
4

Azért tervezték így, mert

Poetro · 2013. Feb. 4. (H), 11.52
Azért tervezték így, mert böngésző oldali JavaScript-ben 99%-ban egyébként is így használod.

document.body.addEventListener('click', callbackFunction1);
var worker = new Worker('my_task.js');
worker.onmessage = callbackFunction2;
var xhr = new XMLHTTPRequest();
xhr.onload = callbackFunction3;
Kezdetben a Node.js 0.1 körül még a Promise-okat használtak (amiről nemrég tartottam előadást a budapest.js keretein belül), de végül áttértek a jelenlegi szintaxisra, mert ez könnyebben összeegyeztethető a böngészőbeli használattal.
7

Böngésző?

Hidvégi Gábor · 2013. Feb. 4. (H), 12.11
De mi most nem a böngészőben, hanem a webszerveren vagyunk.

Van egy feladatom, egy helyről kérek adatokat, amit fel kell dolgoznom, addig nem tudok továbblépni. Jelent-e számomra pluszt, hogy a backend ezt szinkron vagy aszinkron oldja meg? Nem. Én a függvényemben csak aszinkron módon tudok programozni. Akkor mi az értelme? Miért kell a hardver kábelezésének kilátszania? Csak rontja az összképet.

Körülbelül tíz éve az első cégemnél a Generali biztosítónak volt egy fejlesztésünk, amiben én közvetlenül nem vettem részt, de BlaZe talán igen, ő meg tud erősíteni. Ott az volt a programozók elképzelése, hogy mivel az oldal különböző boxokból, építőtéglákból áll össze, legyen benne az URL-ben, hogy melyeknek kell az oldalon szerepelniük, tehát nagyjából így nézett ki, csak sokkal csúnyább és hosszabb volt:
akarmi.php?tegla1&tegla2=ertek&tegla3
Erre kérdeztem én, hogy mi az értelme, hisz van mondjuk tízfajta oldalunk, adjunk nekik aliast, és nem lóg ki a bele a dolognak, hívjuk őket oldal1.php-nek meg oldal2.php-nek, klasszikus szép url probléma, és analóg ezzel a blokkoló-nem blokkoló dologgal.
8

A Generaliban én sem vettem

BlaZe · 2013. Feb. 4. (H), 12.24
A Generaliban én sem vettem részt, később érkeztem. De amennyire emlékszem, ott a mögöttes elgondolás az volt, hogy több modulnak is tudjanak különböző actionöket küldeni 1 requestben.

Itt most kicsit másról van szó. Az aszinkron lényege nagyon tömören, hogy valamit futtatásra jelölsz, és azt majd valahol valaki lefuttatja, de a hívás helyén ezt neked nem kell tudni hogyan lesz meghajtva, te csak majd valamikor megkapod annak az eredményét. Ez több előnnyel is jár, de alapvetően elég jól skálázható, kontrollt ad a kezedbe a terhelés fölött.

Ha a közös munkahelyünkről keresel példát, akkor pl az exit keresője volt aszinkron megoldva.
15

Én a függvényemben csak

Hidvégi Gábor · 2013. Feb. 4. (H), 15.03
Én a függvényemben csak aszinkron módon tudok programozni.
Ezt természetesen elírtam, helyesen: Én a függvényemben csak szinkron módon tudok programozni, azaz:

utasítás 1
utasítás 2
utasítás 3

Ezek egymásra épülnek.
3

Folytatás

Hidvégi Gábor · 2013. Feb. 4. (H), 11.43
Biztos vagyok benne, hogy maga a node.js nagyon jó alapokkal rendelkezik, és hatékonyabb programokat lehetne benne írni, mint php-ben, de szerintem amíg erre a callback-es dologra nem adnak alternatívát, addig a játékszer kategóriánál nem léphet följebb az egész.

I also realized that strong typing, which I had been worshiping for years, was somewhat of a neurotic thing. It makes you feel safe but you pay a high price for it (you write a lot of code just to please the compiler) and it actually misses the point (you should be relying on unit tests that check the semantics rather than on compilers that only check formal constraints).
Az mloc.js konferencia előadásainak a fele arról szól, miért használjunk más, erősen típusos vagy statikus típusos nyelvet, amiből aztán JS-t fordíthatunk.

Számomra ez az egész ellentmondásos. Most akkor jó vagy rossz a JS változókezelése?

Szerintem ez az egész arról szól, hogy vannak programozók, akik egy bizonyos nyelvvel eltöltöttek sok időt, aztán kapnak egy JS projektet (vagy csak elkezdenek vele spontán ismerkedni, mert most a csapból is ez folyik), ahol a megszokott dolgaikat (változó típusossága, objektumorientált programozás stb.) nem találják, és ezért mindenféle eszközöket és mintákat találnak ki, hogyan tudják JS-ben megoldani.

A magam tapasztalatából úgy látom, hogy ezekre nincs szükség. A JS meglévő eszközeivel (például prototípus alapú öröklődés, típus nélküli változók) ugyanúgy kiváló programokat lehet készíteni, mint más nyelveken, csak picit másképp kell gondolkodni közben (tehát a változókezelése jó). Ha egy másik eszközre vagyunk utalva, mondjuk egy fordítóra, ami egy bizonyos nyelvből JS-t csinál, csak a hibalehetőségek számát növeljük.
5

maga a node.js nagyon jó

Poetro · 2013. Feb. 4. (H), 11.55
maga a node.js nagyon jó alapokkal rendelkezik, és hatékonyabb programokat lehetne benne írni, mint php-ben, de szerintem amíg erre a callback-es dologra nem adnak alternatívát

Szerintem valaki túl sokat programozott PHP-ben ;)
akik egy bizonyos nyelvvel eltöltöttek sok időt, aztán kapnak egy JS projektet (vagy csak elkezdenek vele spontán ismerkedni, mert most a csapból is ez folyik), ahol a megszokott dolgaikat (változó típusossága, objektumorientált programozás stb.) nem találják, és ezért mindenféle eszközöket és mintákat találnak ki

Mert én pont ugyanezt látom a callback-ekkel való idegenkedésben.
6

Mi a bajod igazából az

BlaZe · 2013. Feb. 4. (H), 12.09
Mi a bajod igazából az aszonkronitással? Illetve mit kéne kitaláljanak callback helyett? Hogy kéne működjön egy aszinkron hívás?
9

Az aszinkron hívásoknak így

MadBence · 2013. Feb. 4. (H), 13.23
Az aszinkron hívásoknak így kellene kinézniük. A kód kinézete alapján fájdalommentes megoldás, egyedül az értelmezőnek kell kicsit okosabbnak lennie.
Volt is már ilyen kezdeményezés CoffeeScript oldalon, ahol nyelvi elem lett az await és a defer (csak én speciel magával a CoffeeScript nyelvvel nem szimpatizálok).
Ahogy tudom, ilyesmi nincs sem a mostani, sem a következő ES szabványokban.
10

Jobban néz ki valóban, de ez

BlaZe · 2013. Feb. 4. (H), 13.37
Jobban néz ki valóban, de ez is csak elfedi a mögöttes működést (ami persze fontos). És szerintem node.js-ben is egyszerű összehozni egy ilyesmit, csak függvényhívás lesz belőle, nem nyelvi elem.
12

Akkor valamit én

MadBence · 2013. Feb. 4. (H), 14.16
Akkor valamit én félreértettem :). Nem az lenne a cél, hogy elfedjük a fejlesztő elől, hogy a háttérben egy bonyolult aszinkron hívás történik?
Őszintén szólva engem annyira a callback-piramis sem zavar, számomra átlátható (szerintem ez megszokás kérdése). Nyilván, ha a lehetőség adott, akkor valami Promise szerű dolgot használok, mert elfedi a működés egy részét, nekem kevesebbet kell kódolnom, többé-kevésbé szekvenciálisan történik a végrehajtás a kódban (jobban mondva annak látszik).
Ha lenne valami await/async nyelvi elem, akkor pedig azt használnám. Nem azért, mert valami mágikus dolgot csinálnak, hanem mert tudom, hogy nekem egyszerű használni, a háttérben pedig tudom, hogy mi történik.
13

A válaszod tök jogos volt :)

BlaZe · 2013. Feb. 4. (H), 14.38
A válaszod tök jogos volt :) Én azért kérdeztem, amit kérdeztem, mert nem értem, hogy HG-nak mi a baja valójában az aszinkronosdival, illetve hogy ehelyett hogy kéne működjön.
14

Magával az aszinkronitással

Hidvégi Gábor · 2013. Feb. 4. (H), 14.58
Magával az aszinkronitással nincs gondom, sőt, ha nem lenne a node-ban, akkor lennék igazán ideges. Amivel baj van, az a megvalósítása, és egyébként a blogmark is erről szól, hadd idézzem az író példáját:
For example I could write something as simple as:

total += fs.readFile(path).length;
Now, despite all the patterns that I have found, I still have to write something like:

fs.readFile(path, function(err, data) {
total += data.length;
// continue here ..
});
Nem konzekvens, ahogy az egészet kezelik. Valamilyen szinten minden függvény IO-t kezel, tehát ennyi erővel az összeshez adhattak volna callback-et:

Math.abs(-5, function(err, data) {
  valtozo = data;
});

Közben memóriát olvasunk, írunk, ami több nagyságrenddel lassabb, mint a processzor, de amíg elkészül, a CPU addig sem áll meg, hanem más szálakon dolgozhat.

Tehát még egyszer kérdem: kit érdekel, hogy egy függvény hívását szinkron vagy aszinkron módon kezeli a rendszer? Milyen információt hordoz a számomra ennek a tudása? Az én szempontomból csak az a fontos, hogy fusson le és adjon vissza valamilyen értéket, de hogy ezt hogyan teszi, az nem releváns. Felőlem azt nem blokkol, amit nem akar. Emiatt látom fölöslegesnek a callback-eket.
16

Te programod

Poetro · 2013. Feb. 4. (H), 15.22
Tehát még egyszer kérdem: kit érdekel, hogy egy függvény hívását szinkron vagy aszinkron módon kezeli a rendszer? Milyen információt hordoz a számomra ennek a tudása? Az én szempontomból csak az a fontos, hogy fusson le és adjon vissza valamilyen értéket, de hogy ezt hogyan teszi, az nem releváns. Felőlem azt nem blokkol, amit nem akar.

A probléma ott van, hogy a te programod futása blokkolódik, ergo nem tudsz újabb kéréseket kiszolgálni (hacsak nem eleve új szálon kezded meg a kérés kezelését, ekkor pedig a szálak kezelésével kell foglalkoznod, ami szintén bonyolult probléma). Ugyanakkor ha számodra nem probléma, hogy a programod futása szünetel, akkor használhatod a függvények szinkron megfelelőit.

Az Apache HTTPD és más HTTP szerverek pont ezt a blokkolást rejtik el a felhasználó elől, mivel minden egyes kérést új szálban, vagy ahhoz hasonló kontextusban kezelnek, így önmaguk csinálnak aszinkron callback-eket.
  1. Kérés megérkezik a HTTP szerverhez.
  2. Indítunk egy új szálat, vagy ahhoz hasonlót, és várunk, hogy az visszatérjen (callback).
  3. Az elindított szál visszatér az webszervernek szükséges objektummal (vagy időtúllépéssel elszáll) - ez igazából a callback.
  4. A webszerver visszaadja megfelelő formátumban a kapott adatot.
18

Működés

Hidvégi Gábor · 2013. Feb. 4. (H), 15.53
Nem tudok elég érthetően fogalmazni, úgy látszik. Már múltkor leírtam, hogy tisztában vagyok, hogyan működik a node.js nem blokkolása. Nem azt állítom, hogy ez rossz, mert alapvetően egy jó elgondolás, tényleg szuper. Minden jó úgy aszinkron módon, ahogy van.

De.

Amikor van egy programom, abban vannak egymás után az utasítások. Ezek egy logikai egységben egymásra épülnek, vegyük ezt a leegyszerűsített példát (C-szerű szintaktikával):

1. sor: eredmeny = query('SELECT * FROM tábla');
2. sor: for (sor in eredmeny) {
3. sor:  print '<div>sor:' + eredmeny[sor] + '</div>';
4. sor: }

A 2. sor utasítását akkor tudom végrehajtani, ha az első eredményét megkaptam, amíg nincs meg, azaz IO történik, a programom futása áll. Hogy ezt a háttérben a futtatókörnyezetem hogyan oldja meg, az nem az én dolgom. Abban viszont biztos vagyok, hogy nem állítja le a többi program futását, azaz nem blokkol. De az enyémet muszáj, nem ugorhat az 1. sor végrehajtása közben a 2.-ra. Emiatt ezt callback-ekkel megvalósítani továbbra sem látom értelmét, mert csak hosszabb lesz a kód, de nem nyerünk vele semmit.
17

Tehát még egyszer kérdem: kit

BlaZe · 2013. Feb. 4. (H), 15.30
Tehát még egyszer kérdem: kit érdekel, hogy egy függvény hívását szinkron vagy aszinkron módon kezeli a rendszer?

Egyrészt muszáj tudnod, mert az aszinkronnak pont az a lényege, hogy (valószínűleg) nem tér vissza azonnal értékkel. Ezért a válasz feldolgozását meg kell írnod valamilyen módon. Hogy ez callback, vagy más, az most tökmindegy.

Másrészt, ha nem tudod, hogy egy fv aszinkron, akkor nem is tudsz felkészülni az aszinkron működésére. Ha egy hívás result = asyncFunc() formájú lenne, akkor mitől lenne aszinkron?

Tehát elég komoly jelentősége van, hogy ezt tudd.

Illetve az IO-ban is van egy kis kavar a fejedben szerintem. Miközben a CPU a memóriát írja/olvassa, nincs üresjáratban (tekintsünk most el a hw belső működésétől). I/O művelet végzése közben pedig üresjáratban van, és ha nem tudna közben mást csinálni, akkor blockolna, vagyis kihasználatlanul hagyná az erőforrásokat értelmetlenül. Szerintem az okozza nálad a kavart, hogy hozzászoktál a multithread környezethez, ahol ilyen esetben mélyebb szinten az ütemező átadja a vezérlést egy másik szálnak. De a te szálad akkor, ott is vár. Itt ez definíció szerint kicsit máshogy van a node.js felépítése miatt.
19

Illetve az IO-ban is

Joó Ádám · 2013. Feb. 4. (H), 16.51
Illetve az IO-ban is van egy kis kavar a fejedben szerintem. Miközben a CPU a memóriát írja/olvassa, nincs üresjáratban


Dehogynem, wait state-nek hívják.
20

Persze, de ahogy írtam is,

BlaZe · 2013. Feb. 4. (H), 17.20
Persze, de ahogy írtam is, nyelvi szinten nézzük a helyzetet, és nyelvi szinten a változó írása/olvasása atomi művelet, vagyis ezen a szinten a program "nem vár" (nem tud ezalatt az idő alatt mást csinálni). Hogy ez mélyebben nem teljesen így néz ki, az most kicsit más tészta, nem arról beszélünk.
21

Tisztán nyelvi szempontból

Joó Ádám · 2013. Feb. 4. (H), 17.22
Tisztán nyelvi szempontból szerintem a függvényhívás sem más.
22

Nem is mondtam, hogy más.

BlaZe · 2013. Feb. 4. (H), 17.25
Nem is mondtam, hogy más. IO-ról beszéltünk, ahol effektíve perifériára várunk (ezalatt tud mást csinálni a program várakozás helyett).
23

Magas szintű nyelvek esetén

Joó Ádám · 2013. Feb. 4. (H), 17.40
Magas szintű nyelvek esetén az IO is csak függvényhívás, ami azonnal végrehajtódik. Mi persze tudjuk, hogy ez nem igaz, de ez nem változtat ezen. Éppenséggel az értékadást is végezhetnénk aszinkron módon, és a memóriára várva adhatnánk számítási feladatot a processzornak, csak nyilván nem éri meg. A memória is periféria, csak szorosan integrált.
25

Igen, de nézd vissza, hogy

BlaZe · 2013. Feb. 4. (H), 18.05
Igen, de nézd vissza, hogy miről szól ez a topic :) A lényeg, hogy ha nagyobb adatmennyiséget szeretnél megmozgatni perifériáról, vagy lassabb perifériával dolgozol, akkor a várakozás helyett tudsz mást csinálni. Memória pedig egységnyi elérési idővel rendelkezik, azon nem tudsz spórolni, hisz mindent a memóriában tárolsz a programodhoz.
27

Gábor épp ezt mondta, és te

Joó Ádám · 2013. Feb. 4. (H), 18.24
Gábor épp ezt mondta, és te ennek kapcsán írtad, hogy nem érti az IO-t. Nyilván a gyakorlatban nem lehet kihasználni a memóriaelérés alatti üresjáratot.
11

Játékszer???

blacksonic · 2013. Feb. 4. (H), 14.05
Van már jópár oldal ami igazolja hogy nem csak játékszer a Node.js .
Stackoverflown nagyon jó leírások vannak, hogy miért is jó link.

Asszinkronitásban az a jó ha van egy kérésnél több olyan feladat, amik időigényesek, azokat asszinkron callbackekkel tudod pl. párhuzamosítani.
Elsőre még ami tetszett benne, hogy bejövő kérésnél nem külön threadet csinál, hanem csak heap memóriát foglal neki.
24

Kezdem úgy érezni, hogy az

Joó Ádám · 2013. Feb. 4. (H), 17.52
Kezdem úgy érezni, hogy az aszinkron megoldás egyetlen előnye a szinkronnal szemben az alacsony memóriaigény.

Szinkron vezérlés esetén ugyanis a callback minden, ami a hívás után következik a kódban. Amennyiben egy IO-t nem igénylő számítás ideje alatt a kódnak más hasznos munkát is kellene végezni, akkor azt új szál indításával lehet megoldani. Az új szálnak azonban saját stacket kell foglalni, az ütemezését pedig a kernel végzi, a context switch pedig drága.

Aszinkron vezérlés esetén a rendszer az ütemező feladatát hárítja át a fejlesztőre, hogy ne kelljen bevonni a kernelt, ami lényegében alacsonyabb szintű kódot eredményez.

Eddig úgy tűnhet, hogy teljesítmény szempontjából az aszinkron megoldás nyer, azonban amint IO-ról van szó, a helyzet más, ugyanis a context switch ilyenkor nem kerülhető el, a hívást a kernel hajtja végre.

Ugyanannak a problémának, mégpedig a párhuzamosításnak a két különböző megközelítéséről van tehát szó, amelyben a szinkron kód mindenféleképpen magasabb absztrakciós szintet képvisel. Az egyetlen hátránya az, hogy a jelenlegi operációs rendszerek mind egyedi, statikus stacket foglalnak a szálaknak, ami magas párhuzamosság mellett magas memóriaigényhez vezet. Amennyiben erre sikerülne megoldást találni, a teljesítménybeli különbség jórészt eltűnne.

Javítson ki, aki másképp látja.
26

Memóriaigény

Hidvégi Gábor · 2013. Feb. 4. (H), 18.14
node.js esetében a feldolgozás ugyanúgy egy szálon fut, mint PHP-ban. Nem igazán látom a levezetésed alapján, mitől lenne alacsonyabb a memóriaigénye (azaz talán, de ha van is különbség, az minimális) és miért gyorsabb az aszinkron kód, kontext switch mindig kell.
28

node.js esetében a

Joó Ádám · 2013. Feb. 4. (H), 18.40
node.js esetében a feldolgozás ugyanúgy egy szálon fut, mint PHP-ban


Ez így nem igaz, PHP-ban (valójában Apache-ban) kapcsolatonként egy szálon, míg a Node.js-ben szerverenként egy szálon. Ezért magasabb az előbbi memóriaigénye: minden szál rendszerszinten előre beállított méretű stackkel jön létre, függetlenül attól, szükség van-e rá. A 8 kilobyte-os Debian alapbeállítás mellett ez 10 000 élő kapcsolatnál közel 80 megabyte.

és miért gyorsabb az aszinkron kód, kontext switch mindig kell


Jogos. Amikor írtam, arra gondoltam, hogy ha csak egy számításigényes műveletet akarsz aszinkron végezni, hogy lehetőséget adj más kérések kiszolgálásának, akkor nincs szükség context switchre, de ilyenkor időzítőt kell használj, ahhoz pedig megint csak a kernelt kell hívni.
29

Memória

Hidvégi Gábor · 2013. Feb. 4. (H), 18.51
Ja, hogy a memóriaigényt úgy értetted. Igen, ez nyilvánvaló, hogy az Apache+PHP páros több memóriát eszik, viszont jobban is skálázható, mert az egyes szálakat az operációs rendszer akárhány magra ki tudja osztani. Az egyszálas futást arra írtam, hogy miután elindul egy-egy script feldolgozása, az már viszont abban a szálban marad.
31

"Igen, ez nyilvánvaló, hogy

BlaZe · 2013. Feb. 4. (H), 18.56
Igen, ez nyilvánvaló, hogy az Apache+PHP páros több memóriát eszik, viszont jobban is skálázható, mert az egyes szálakat az operációs rendszer akárhány magra ki tudja osztani.


Pont hogy nem, és ez az igazi előnye az event-drivennek a process baseddel szemben. Event-drivennél te mondod meg, hogy hány worker fut egyszerre (és hol), process basednél pedig elindul párhuzamosan párszáz szálon a feldolgozás, aztán adott esetben nézel, hogy mi van :)
32

A PHP is event driven, csak

Hidvégi Gábor · 2013. Feb. 5. (K), 08.39
A PHP is event driven, csak az eseménykezelést elrejti a felhasználó elől, ahogy Ádám is írta a 24-ben, egy magasabb absztrakciós szintet ad.
33

Csak addig nem nagyon tudsz

Poetro · 2013. Feb. 5. (K), 10.05
Csak addig nem nagyon tudsz semmit csinálni a programodban, amíg az adatbázislekérés, vagy pl. futó utasítás fut.
35

1, Nem is akarok. Azért

Hidvégi Gábor · 2013. Feb. 5. (K), 12.15
1, Nem is akarok. Azért indítottam el az adatbázislekérést, mert szükségem van az eredményre, anélkül nem tud az aktuális függvényem továbbmenni.

2, Még ha tudnék is az adatbáziskéréssel párhuzamosan csinálni bármit, az semmi komolyabb nem lehet, mivel az aktuális scriptem egy szálon fut (node.js-ben és PHP-ben is). Tehát, ha visszakerül a vezérlés a scriptemhez az adatbázistól, és még épp dolgozik valamin, akkor várni kell, amíg a munka befejeződik, hogy az adatbázistól visszakapott válasszal kezdeni tudjak valamit. Mivel nem tudom, mennyi idő az adatbázistól való kérés feldolgozása, ezért legfeljebb pár egyszerűbb utasítást adhatok ki. Amennyiben úgy jön ki, hogy az adatbázislekérés szála ugyanazon a processzoron fut, mint a scriptemé, a párhuzamosan kiadott script utasítások lassíthatják az adatbáziskérés feldolgozását.

Tehát node.js esetében igaz, futhat tovább a scriptem adatbázislekérés vagy bármely más IO mellett, de kérdéses, hogy mit nyerhetek vele, továbbá a rendszer nagyon alapos ismerete kell hozzá.

3, csak a MySQL és a PostgreSQL működését ismerem, de, feltételezem, hogy a többi is hasonló. A lekérés indítása (mysql_query()) PHP-ban valóban blokkolja a script futását úgy, hogy a válasz (hogy maga a lekérés sikeres volt-e) megérkezéséig nem tudok csinálni semmit. Viszont siker esetén az adatok beolvasása (mysql_fetch_assoc()) mindkét esetben ugyanúgy történik:

node.js:
function dump_rows(cmd) {
  cmd.addListener('row', function(r) {
    parancs1(r);
    parancs2(r);
    parancs3(r);
  });
}

var cmd = db.query("select 1+1,2,3,'4',length('hello')");
dump_rows(cmd);


PHP:
$cmd = mysql_query("select 1+1,2,3,'4',length('hello')");
while ($r = mysql_fetch_assoc($cmd)) {
  parancs1($r);
  parancs2($r);
  parancs3($r);
}

A PHP esetében a mysql_fetch_assoc() kvázi ugyanúgy nem blokkoló utasítás, mint ahogy a node működik. A node.js példát a mysql-native-pre oldalról vettem. Érzésem szerint így is meg lehetett volna írni:
db.query("select 1+1,2,3,'4',length('hello')").addListener('row', function(r) { ... });
de ez a lényegen nem változtat.
36

1, Nem is akarok. Azért

BlaZe · 2013. Feb. 5. (K), 12.45
1, Nem is akarok. Azért indítottam el az adatbázislekérést, mert szükségem van az eredményre, anélkül nem tud az aktuális függvényem továbbmenni.

Egy weboldalon egy oldalsó "fórum utolsó bejegyzések" boxot akarsz feltölteni tartalommal, de a requested alapvetően egy megrendelés feladásáról szól. Az utolsó hozzászólásokat simán lekérheted aszinkron, mert az eredményére csak a html kód generálásánál lesz szükséged, közben a megrendelést fel tudod dolgozni.
37

Ez így igaz, ha tudod

Hidvégi Gábor · 2013. Feb. 5. (K), 12.58
Ez így igaz, ha tudod garantálni, hogy az SQL processz másik processzoron fut, mint a scripted.
38

Nálam ez az utóbbi 7-8 évben

Poetro · 2013. Feb. 5. (K), 15.06
Nálam ez az utóbbi 7-8 évben így volt, és ez alapvető megközelítés, ha skálázható alkalmazást fejleszt az ember. Valamint te valamiért nagyon ragaszkodsz az SQL adatbázishoz, holott valószínűleg te is több tucat más forrásból is olvasol be adatot: SOAP, XMLRPC, JSON, fájlrendszer, NFS, ssh, külső process-ek stb.
42

Szerencsésnek mondhatom

Hidvégi Gábor · 2013. Feb. 7. (Cs), 17.03
Szerencsésnek mondhatom magam, mert csak SQL-lel kell dolgoznom, mástól nem függök. A lényegen egyébként nem változtat, mert IO mind.
39

Tipikusan Node-ban sem tudsz

Joó Ádám · 2013. Feb. 5. (K), 16.24
Tipikusan Node-ban sem tudsz mást csinálni, olyankor másik kapcsolattal foglalkozik, ugyanúgy, ahogy a többszálas környezetben is.
34

Nem egyről beszélünk megint.

BlaZe · 2013. Feb. 5. (K), 12.12
Nem egyről beszélünk megint. Én event-driven webserverről beszéltem, szemben a process-baseddel, mint pl az apache. Linkek a témában pl itt és itt.

A PHP egyébként nem event-driven. Hogy a scripted alatt mi hogy mit varázsol, az teljesen más kérdés. Akkor minden event-driven, mert a kernel/cpu okos :) De a nyelv, környezet maga nem az. A PHP egy single-threaded interpreter nyelv, blokkoló perifériakezeléssel, és nem tudsz benne aszinkron hívásokat csinálni.
30

Van különbség a process based

BlaZe · 2013. Feb. 4. (H), 18.52
Van különbség a process based apache és az event-based kiszolgálás között. A memórianyereség alapvetően itt jelentkezik, nem a script futása közben.
40

Caolan async-el sikerült

inf · 2013. Feb. 7. (Cs), 14.35
Caolan async-el sikerült ennyire redukálnom az eredeti kódot:

function du(path, callback) {
	async.waterfall([
		function info(summarize){
			fs.stat(path, summarize);
		},
		function summarize(stat, result){
			if (stat.isFile())
				fs.readFile(path, function(err, data) {
					if (err)
						return result(err)
					result(null, data.length);
				});
			else if (stat.isDirectory())
				fs.readdir(path, function(err, files) {
					if (err)
						return result(err);
					var total = 0;
					async.forEach(files, function (file, done){
						du(path + "/" + file, function(err, length) {
							if (err)
								return done(err);
							total += length;
							done();
						});
					}, function (err){
						result(err, total);
					});
				});
			else
				result("odd file");
		}
	], callback);
}
Nem biztos, hogy működik, nem teszteltem. Több tapasztalattal talán még szebbet is ki lehetne hozni belőle. Én nem érzem ezt az egészet akkora problémának. Próbálnak thread-et, fiber-t, kód átfordítót, meg egy csomó mindent ráerőltetni nodejs-re emiatt, pedig szerintem teljesen szükségtelen.

Akár teljesen ki is lehet váltani waterfall-al az error check-eket, meg szétdarabolni az egészet több függvénybe:

var du = function (){
	var drive = {
		usage: function (path, next) {
			async.waterfall([
				function (next){
					fs.stat(path, next);
				},
				function (stat, next){
					if (stat.isFile())
						drive.fileSize(path, next)
					else if (stat.isDirectory())
						drive.dirSize(path, next)
					else
						next("odd file");
				}
			], next);
		},
		fileSize: function (path, next){
			async.waterfall([
				function (next){
					fs.readFile(path, next);
				},
				function (data, next){
					next(null, data.length);
				}
			], next);
		},
		dirSize: function (path, next){
			async.waterfall([
				function(next){
					fs.readdir(path, next); 
				},
				function(files, next){
					var total = 0;
					async.forEach(files, function (file, done){
						async.waterfall([
							function (next){
								drive.usage(path + "/" + file, next);
							},
							function (length, next){
								total += length;
								next();
							}
						], done);
					}, function (err){
						next(err, total);
					});
				}
			], next);
		}
	};
	return drive.usage;
}();
Picit tényleg zavaró, hogy minden aszinkron hívásnál meg kell törni az algoritmust, de ha nem jeleznénk, hogy aszinkron hívás van, akkor elég nagy bajban lennénk, ha értelmezni akarnánk a kódot...

Imho lehetne async.parallel és async.series nyelvi elemeket betenni js-be, és ezzel meg lenne oldva a probléma. Alapból minden függvény szinkron menne, ezekkel a nyelvi elemekkel meg lehetne az aszinkron kéréseket jelezni olyan helyeken, ahol tényleg szükség is van rájuk. Erre mondjuk nem egyszerű fordítót írni, ha azt nézzük, hogy minden aszinkron van megírva, és a szinkron kódot valahogyan aszinkron formára kellene hozni végső soron.

Írtam egy kis kódot, amivel sikerült tovább egyszerűsítenem. Többek között kiemeltem az err ellenőrzését a callback-ek elejéről.

Function.prototype.err = function (next){
	var f = this;
	return function (err){
		if (err)
			next(err);
		else {
			var args = Array.prototype.slice.call(arguments, 1);
			args.push(next);
			f.apply(this, args);
		}	
	};
};

Function.prototype.waitSeries = function (){
	var f = this;
	for (var i=arguments.length-1; i>=0; --i)
		f = arguments[i].err(f);
	return f;
};
Így aszinkron:

function du (path, next) {
	next.waitSeries(fs.stat, function (stat, next){
		if (stat.isFile())
			next.waitSeries(fs.readFile, function (data, next){
				next(null, data.length);
			})(null, path);
		else if (stat.isDirectory())
			next.waitSeries(fs.readdir, function(files, next){
				var total = 0;
				async.forEach(files, function (file, done){
					done.waitSeries(du, function (length, next){
						total += length;
						next();
					})(null, path + "/" + file);
				}, function (err){
					next(err, total);
				});
			})(null, path);
		else
			next("odd file");
	})(null, path);
}
Érdemes összehasonlítani a szinkronnal:

function du (path) {
	var stat = fs.stat(path);
	if (stat.isFile())
		return fs.readFile(path).length;
	else if (stat.isDirectory()) {
		var total = 0;
		_.each(fs.readdir(path), function (file){
			total += du(path + "/" + file);
		});
		return total;
	}
	else
		throw "odd file";
}
Ha még a total feldolgozását is megtámogatnám saját függvénnyel, akkor el lehetne érni hasonló tömörséget, viszont még mindig túl sok a zaj. Azt hiszem tákolok még egy kicsit a saját lib-emen, kíváncsi vagyok meddig lehet javítani a dolgon.
41

Szinkron

Hidvégi Gábor · 2013. Feb. 7. (Cs), 17.01
Valamelyik nap én is ugyanarra a következtetésre jutottam, mint te az utolsó bekezdésben. A fordítót pedig nem is kéne módosítani, mert, mint korábban kiderült, már tudja ezt (gondolok itt arra, hogy akár a Math.abs()-t is megírhatták volna callback-kel.). Matematikailag pedig olyan kicsi az esélye, hogy valóban nyersz azon, ha párhuzamosan indítasz el folyamatokat (mindegyiket külön vas dolgozza fel), hogy inkább a normális, szinkron programozást kéne támogatni.
43

Hát szerintem is jobb lenne,

inf · 2013. Feb. 7. (Cs), 17.19
Hát szerintem is jobb lenne, ha alapból szinkron menne minden, és az aszinkron hívás lenne a speciális, nem pedig fordítva.
44

A node.js-t nem azért

sylvanus · 2013. Már. 14. (Cs), 22.42
A node.js-t nem azért találták ki hogy kényelmesebb legyen a programozók élete, hanem főként azért, hogy egy nagyon hatékony eszközt adjon a webszerverek optimalizáláshoz. A legtöbb mai webszervernél radikális teljesítménynövekedést lehet elérni node.js-sel a hagyományos process based felálláshoz képest. Ez persze alkalmazásfüggő, nem jelent mindenre megoldást. Ha kényelmesen akarsz programozni, és nem érdekel a teljesítmény akkor tényleg nem ajánlott a node.js. Ilyenkor jobb inkább magasabb szinten programozni.