javascript - Asynkron funksjon i en while-løkke
Jeg har et spørsmål om hvordan man utfører en asynkron oppgave i en while-løkke til en viss betingelse er oppfylt. Dette er mer et teoretisk spørsmål, men jeg kan se hvordan dette kan være et problem i noen scenarier.
Jeg vil prøve å demonstrere problemet med et eksempel (jeg bruker JavaScript her, men du kan bruke hvilket som helst språk):
Jeg kunne ha en enhet, og jeg vil holde applikasjonen min til den enheten har nådd en spesifikk tilstand. Hvis metoden som jeg kan bruke for å hente tilstanden til enheten er synkron, kan koden se slik ut:
// Hold until the desired state is reached
var state = false;
while (!state) {
state = device.getStateSync();
}
// [...] continue the program
Mitt spørsmål nå er: Hvordan kan jeg transformere denne koden når alt jeg får fra enheten er en asynkron getState-funksjon? Koden bør fungere uansett hvor lang kjøretiden til kallet er, og bør ta hensyn til at jeg jobber med begrenset minne og stakkstørrelse.
// [...] This would call the async function in a loop and crash the program
while (!state) {
// [...] something
device.getStateAsync(function(newState) {
state = newState;
});
// [...] something else
}
Dette ene innlegget som jeg fant har en rekursiv løsning (http://blog.victorquinn.com/javascript-promise-while-loop). Selv om dette er en fin løsning, vil det på et tidspunkt støte på problemer med stakkstørrelsen, hvis løkken kalles for ofte.
Akkurat nå har jeg en magefølelse av at det kanskje ikke finnes noen løsning. Vet du om noen måte å gjøre dette på? Eller vet du hvordan man kan bevise at det ikke finnes noen måte å gjøre det på? Føl deg fri til å inkludere mer komplekse konsepter som tråder, løfter eller framtider.
Vennligst husk at dette er et teoretisk spørsmål, og eksemplet er for en situasjon hvor jeg ikke har mulighet til å endre rammen (eller enheten) jeg jobber med.
Takk for alle svar og ideer!
Pedro
Svar #1
I JavaScript kan du ikke lage en løkke som venter på at en tilstand skal endres med mindre koden som faktisk endrer tilstanden er inne i selve løkken eller er en bieffekt av en funksjon som kalles i løkken. Det er fordi JavaScript er enkelttrådet (bortsett fra webworkers som ikke vurderes her), så så lenge en løkke kjører i JavaScript, kan ingen annen kode kjøre, og ingen annen kode kan noen gang endre tilstandsvariabelen din. Du vil rett og slett få en uendelig løkke, ettersom løkken venter på noe som aldri kan endres. Til slutt vil nettleseren klage over at du har ikke-responsiv kode kjørende og stenge den ned.
På grunn av det finnes det ikke ubestemte eller lange venteløkker i JavaScript. Det er mulig å loope i et sekund eller så bare for å la litt tid gå, men det er sjelden nyttig, produktivt eller den beste måten å skrive JS-kode på.
I stedet må du enten utløse en hendelse eller en tilbakekallingsfunksjon når tilstanden endres, og den interesserte koden kan abonnere på den hendelsen eller registrere sin tilbakekallingsfunksjon. Eller, du må sjekke med jevne mellomrom ved hjelp av en timer for å se hva tilstanden har endret seg til (det første alternativet er det foretrukne valget).
Hvis du skulle designe et API der du ønsket å kunne tillate noe kallende kode å vite når en tilstand endres, ville du vanligvis implementere enten en callback eller et løfte. Callback-tilnærmingen kunne se slik ut:
device.registerStateChangeCallback("type of state interested in", fn);
Deretter vil API-et kalle den innsendte tilbakemeldingsfunksjonen når den spesifiserte tilstanden endres til en ny verdi. Det er opp til API-et om dette er en engangsmelding eller om det skjer hver gang tilstanden endres, inntil tilbakemeldingsfunksjonen slettes.
Så, i stedet for å la den som ringer vente i en travel løkke til tilstanden endres, skriver den som ringer asynkron kode (slik fungerer JavaScript for denne typen ting) med en tilbakekallingsfunksjon som vil bli kalt senere når tilstanden endres. For eksempel kan koden til den som ringer se slik ut:
device.registerStateChangeCallback("syncState", function(newState) {
// caller puts code here that wants to do something when
// the syncState has changed
});
Hvis varslingene er ment å være engangs, kan du også bruke løfter, og API-en returnerer ganske enkelt et løfte som blir oppfylt når syncState endres:
device.registerStateChange("syncState").then(function(newState) {
// caller puts code here that wants to do something when
// the syncState has changed
});
Ulempene med løfter er at de er rent enkeltbruk (kun ett varsel), så hvis du vil ha flere varsler, er det bedre å bruke tilbakeringinger. Fordelene med løfter over tilbakeringinger er at de gir mange funksjoner for å synkronisere dem med andre hendelser (som sekvensering, vente på at en serie hendelser skal fullføres, koordinere flere asynkrone ting, osv.) og de gir bedre håndtering av asynkrone feil.
Svar #2
Du kan ikke gjøre dette. Årsaken er at Javascript er enkelttrådet. Mens while-løkka kjører, kjører ingenting annet, så enhver asynkron oppgave som forventes å oppdatere enheten sin tilstand vil aldri få sjansen til å kjøre. Asynkrone oppgaver i Javascript kjøres av hovedhendelseshåndteringsløkka. Dette betyr at asynkrone operasjoner ikke vil kjøre før du returnerer fra skriptet ditt tilbake til hendelseshåndtereren, eller skriptet utfører et kall til hendelseshåndtereren (dette skjer bare når skriptet venter på brukerinput med confirm eller prompt).
