javascript - Aszinkron függvény egy while-ciklusban
Van egy kérdésem arról, hogyan lehet aszinkron feladatot végrehajtani egy while-ciklusban, amíg egy bizonyos feltétel teljesül. Ez inkább elméleti kérdés, de látom, hogy bizonyos helyzetekben ez problémát jelenthet.
Megpróbálom bemutatni a problémát egy példán (itt JavaScript-et használok, de bármilyen nyelvet használhatsz):
Lehetne egy eszközöm, és szeretném visszatartani az alkalmazásomat, amíg az az eszköz el nem éri a kívánt állapotot. Ha a módszer, amellyel lekérdezhetem az eszköz állapotát, szinkron, a kód így nézhet ki:
// Hold until the desired state is reached
var state = false;
while (!state) {
state = device.getStateSync();
}
// [...] continue the program
A kérdésem most az: Hogyan tudom átalakítani ezt a kódot, ha a készüléktől csak egy aszinkron getState függvényt kapok? A kódnak működnie kell bármennyi ideig tart a hívás végrehajtása, és figyelembe kell venni, hogy korlátozott memóriával és veremmérettel dolgozom.
// [...] This would call the async function in a loop and crash the program
while (!state) {
// [...] something
device.getStateAsync(function(newState) {
state = newState;
});
// [...] something else
}
Ez az egyik bejegyzés, amit találtam, egy rekurzív megoldást tartalmaz (http://blog.victorquinn.com/javascript-promise-while-loop). Bár ez egy szép megoldás, egy bizonyos ponton problémákba fog ütközni a veremtér méretével, ha a ciklust túl gyakran hívják.
Most az az ösztönös érzésem, hogy lehet, hogy nincs megoldás. Tudsz valamilyen módot erre? Vagy tudod, hogyan lehet bizonyítani, hogy nincs mód rá? Nyugodtan említhetsz bonyolultabb fogalmakat is, mint a Szálak, Ígéretek vagy Jövők.
Kérlek, vedd figyelembe, hogy ez egy elméleti kérdés, és a példa egy olyan helyzetre vonatkozik, ahol nincs lehetőségem megváltoztatni a keretrendszert (vagy eszközt), amivel dolgozom.
Köszönöm minden válaszodat és ötletedet!
Pedro
Válasz #1
Javascriptben nem tudsz ciklust futtatni, miközben vársz, hogy egy feltétel változzon, hacsak az a kód, amely ténylegesen megváltoztatja a feltételt, nincs ugyanabban a ciklusban, vagy nem valami függvény mellékhatása, amit a ciklusban meghívnak. Ennek oka, hogy a javascript egyszálú (a webworkerek kivételével, melyek itt nem számítanak), így amíg egy ciklus fut a javascriptben, más kód nem tud futni, így semmi más kód nem tudja megváltoztatni a feltételváltozót. Egyszerűen végtelen ciklusba kerülsz, miközben a ciklus valami olyan dologra vár, ami sosem fog változni. Végül a böngésző panaszkodni fog, hogy nem válaszoló kód fut, és leállítja azt.
Ennek következtében nincsenek bizonytalan vagy hosszú várakozási ciklusok a JavaScriptben. Lehetséges egy másodpercig vagy körülbelül addig ciklusozni, hogy eltávolítsunk egy kis időt, de ez ritkán hasznos, produktív vagy a legjobb módja a JS kód írásának.
Ehelyett vagy egy eseményt vagy visszahívást kell kiváltanod, amikor a feltétel megváltozik, és az érintett kód feliratkozhat az eseményre vagy regisztrálhatja a visszahívását. Vagy időzítős lekérdezéssel kell ellenőrizni, hogy a feltétel megváltozott-e (az első lehetőség az előnyben részesített választás).
Ha olyan API-t terveznél, ahol lehetővé akarod tenni, hogy egyes hívó kódok tudjanak arról, amikor egy állapot megváltozik, általában vagy egy visszahívást (callback), vagy egy ígéretet (promise) valósítanál meg. A visszahívásos megközelítés így nézhet ki:
device.registerStateChangeCallback("type of state interested in", fn);
Ezután az API meghívja a megadott visszahívási függvényt, amikor a megadott állapot új értékre változik. Az API-n múlik, hogy ez egyszeri értesítés legyen, vagy minden alkalommal megtörténjen, amikor az állapot változik, amíg a visszahívás nincs leiratkozva.
Szóval ahelyett, hogy a hívó fél egy foglalt ciklusban várna, amíg az állapot megváltozik, a hívó aszinkron kódot ír (így működik a JavaScript az ilyen esetekben) egy visszahívással, amelyet valamikor később hívnak meg, amikor az állapot megváltozik. Például a hívó kódja így nézhet ki:
device.registerStateChangeCallback("syncState", function(newState) {
// caller puts code here that wants to do something when
// the syncState has changed
});
Ha a értesítések csak egyszeri alkalomra szólnak, akkor használhatsz ígéreteket is, és az API egyszerűen egy ígéretet ad vissza, amely teljesül, amikor a syncState megváltozik:
device.registerStateChange("syncState").then(function(newState) {
// caller puts code here that wants to do something when
// the syncState has changed
});
Az ígéretek hátránya, hogy kizárólag egyszeri használatra valók (csak egy értesítés), így ha több értesítést szeretnél, akkor jobban jársz a visszahívásokkal. Az ígéretek előnye a visszahívásokkal szemben az, hogy sok funkciót biztosítanak az egyéb eseményekkel való szinkronizálásra (például szekvenálás, sorozatos események befejezésének megvárása, több aszinkron dolog koordinálása stb.), és jobb aszinkron hibakezelést biztosítanak.
Válasz #2
Ezt nem tudod megtenni. Ennek az az oka, hogy a Javascript egyszálú. Amíg a while ciklus fut, semmi más nem fut, ezért bármely aszinkron feladat, amelynek frissítenie kellene az eszköz állapotát, soha nem fog lehetőséget kapni a futásra. A Javascript-ben az aszinkron feladatokat a fő eseménykezelő ciklus futtatja. Ez azt jelenti, hogy az aszinkron műveletek nem fognak futni, amíg vissza nem térsz a scriptedből az eseménykezelőhöz, vagy a script nem hajt végre egy hívást az eseménykezelőbe (ez csak akkor történik meg, ha a script a felhasználói bemenetre vár confirm vagy prompt segítségével).
