javascript - Asynkroninen funktio while-silmukassa
Minulla on kysymys siitä, kuinka suorittaa asynkroninen tehtävä while-silmukassa, kunnes jokin ehto täyttyy. Tämä on enemmän teoreettinen kysymys, mutta näen, kuinka tämä voisi olla ongelma joissakin tilanteissa.
Yritän havainnollistaa ongelmaa esimerkillä (käytän tässä JavaScriptiä, mutta voit käyttää mitä tahansa kieltä):
Minulla voisi olla laite ja haluan pitää sovellukseni odottamassa, kunnes kyseinen laite on saavuttanut tietyn tilan. Jos menetelmä, jolla voin saada laitteen tilan, on synkroninen, koodi voisi näyttää tältä:
// Hold until the desired state is reached
var state = false;
while (!state) {
state = device.getStateSync();
}
// [...] continue the program
Minun kysymykseni nyt on: Kuinka voin muuntaa tämän koodin, kun ainoa mitä saan laitteelta on asynkroninen getState-funktio? Koodin tulisi toimia riippumatta siitä, kuinka pitkä kutsun suorittamisaika on, ja sen tulisi pitää mielessä, että työstän rajallista muistia ja pino-kokoa.
// [...] This would call the async function in a loop and crash the program
while (!state) {
// [...] something
device.getStateAsync(function(newState) {
state = newState;
});
// [...] something else
}
Tämä yksi löytämäni julkaisu sisältää rekursiivisen ratkaisun (http://blog.victorquinn.com/javascript-promise-while-loop). Vaikka tämä onkin mukava ratkaisu, jossain vaiheessa se kohtaa pinon koon ongelmia, jos silmukkaa kutsutaan liian usein.
Tällä hetkellä minulla on vatsaefekti, että ratkaisu saattaa olla olematta. Tiedätkö mitään tapaa tehdä tätä? Tai tiedätkö miten todistaa, ettei tapaa tehdä sitä ole? Voit vapaasti sisällyttää monimutkaisempia käsitteitä, kuten säikeitä, lupauksia tai tulevia arvoja.
Otathan huomioon, että tämä on teoreettinen kysymys ja esimerkki koskee tilannetta, jossa minulla ei ole keinoa muuttaa käyttämääni kehystä (tai laitetta).
Kiitos jokaisesta vastauksesta ja ideasta!
Pedro
Vastaus #1
Javascriptissa et voi toistaa silmukkaa odottaen ehdon muuttumista, ellei koodi, joka itse asiassa muuttaa ehtoa, ole saman silmukan sisällä tai osa jonkin silmukassa kutsutun funktion sivuvaikutusta. Tämä johtuu siitä, että javascript on yksisäikeinen (paitsi webworkereiden osalta, joita ei tässä käsitellä), joten niin kauan kuin silmukka toistuu javascriptissa, mikään muu koodi ei voi suorittua, joten mikään muu koodi ei voi koskaan muuttaa ehtomuuttujaasi. Saat yksinkertaisesti äärettömän silmukan, koska silmukka odottaa jotain, joka ei koskaan voi muuttua. Lopulta selain ilmoittaa, että sinulla on vastaamaton koodi käynnissä, ja sulkee sen.
Sen takia JavaScriptissä ei ole määräämättömiä tai pitkiä odotus-silmukoita. On mahdollista silmukoida noin sekunnin verran vain ajan kulumisen odottamiseksi, mutta se on harvoin hyödyllistä, tuottavaa tai paras tapa kirjoittaa JavaScript-koodia.
Sen sijaan sinun täytyy joko laukaista tapahtuma tai takaisinsoitto, kun ehto muuttuu, ja kiinnostunut koodi voi tilata tuon tapahtuman tai rekisteröidä takaisinsoittonsa. Tai sinun täytyy tarkistaa ajastimella, miten ehto on muuttunut (ensimmäinen vaihtoehto on suositeltu valinta).
Jos suunnittelisit APIa, jossa haluaisit sallia joidenkin kutsuvien koodien tietää, kun tila muuttuu, yleensä toteuttaisit joko callbackin tai lupauksen. Callback-lähestymistapa voisi näyttää tältä:
device.registerStateChangeCallback("type of state interested in", fn);
Sitten API kutsuu annettua takaisinsoittofunktiota aina, kun määritetty tila muuttuu uuteen arvoon. On API:n päätettävissä, onko tämä yhden kerran ilmoitus vai tapahtuuko se aina, kun tila muuttuu, kunnes takaisinsoitto poistetaan rekisteröinnistä.
Joten sen sijaan, että soittaja odottaisi ruuhkautuneessa silmukassa, kunnes tila muuttuu, soittaja kirjoittaa asynkronista koodia (näin JavaScript toimii tämän tyyppisissä asioissa) takaisinsoittofunktiolla, joka kutsutaan myöhemmin, kun tila muuttuu. Esimerkiksi soittajan koodi saattaisi näyttää tältä:
device.registerStateChangeCallback("syncState", function(newState) {
// caller puts code here that wants to do something when
// the syncState has changed
});
Jos ilmoitusten on tarkoitus olla kerta kerralta, voit myös käyttää lupauksia, ja rajapinta palauttaa yksinkertaisesti lupauksen, joka täytetään, kun synkronointitila muuttuu:
device.registerStateChange("syncState").then(function(newState) {
// caller puts code here that wants to do something when
// the syncState has changed
});
Lupausten haittapuolena on, että ne ovat puhtaasti yksittäiskäyttöisiä (vain yksi ilmoitus), joten jos haluat useita ilmoituksia, on parempi käyttää callbackeja. Lupauksen etuna callbackeihin verrattuna on, että ne tarjoavat paljon ominaisuuksia niiden synkronointiin muiden tapahtumien kanssa (kuten järjestäminen, odottaminen, että sarja tapahtumia saadaan valmiiksi, useiden asynkronisten asioiden koordinointi jne.) ja ne tarjoavat paremman asynkronisen virheenkäsittelyn.
Vastaus #2
Et voi tehdä tätä. Syynä on se, että Javascript on yksisäikeinen. Kun while-silmukka on käynnissä, mikään muu ei käy, joten mikään odotettu asynkroninen tehtävä, joka päivittäisi laitteen tilaa, ei koskaan pääse suorittamaan. Javascriptin asynkroniset tehtävät suorittaa pääasiallinen tapahtumankäsittelijäsilmukka. Tämä tarkoittaa, että asynkroniset operaatiot eivät käynnisty ennen kuin palaat skriptistäsi takaisin tapahtumankäsittelijälle, tai skripti tekee kutsun tapahtumankäsittelijään (tämä tapahtuu vain, kun skripti odottaa käyttäjän syötettä confirm tai prompt-kutsulla).
