javascript - Hvordan får man adgang til resultatet af et Promise synkront?


Lad mig starte med det faktum, at jeg kan lide asynkron kode. Jeg ville aldrig pakke asynkron kode ind i en synkron wrapper i produktion, men det er stadig noget, jeg gerne vil lære at gøre. Jeg taler specifikt om Node.JS, ikke browseren. Der er mange måder at få adgang til resultatet af en asynkron funktion synkront, for eksempel ved at bruge child_process.spawnSync eller en worker og Atomics. Problemet med disse måder er dette:

let prom = Promise.resolve(4);
// It is now impossible (as far as I know) to access the result of prom synchronously

Løfter kan ikke sendes i et postMessage-kald, så en worker ikke kan få adgang til dem og vente på, at de bliver færdige synkront eller overhovedet. Man kunne tænke, hvorfor ikke gøre dette:

let prom = Promise.resolve(4);
prom.then(res => global.result = res);
while (!global.result) {}; // Do nothing
// Once the loop finishes, the result *would* be available
console.log(global.result); // => 4

Selvfølgelig virker dette ikke. Eventloopet venter på at udføre while-løkken fuldstændigt, før det overhovedet begynder at håndtere callback-funktionen af prom.then. Dette forårsager en uendelig løkke. Så dette får mig til at spørge: "Er der en synkron opgave, som skal udføres normalt for at tillade venten på et løfte?"

Rediger
For resten, jeg forstår fuldstændigt async/await. Hvis jeg bruger et Node.JS modul, kan jeg bare gøre:

let resolved = await prom;

Eller hvis jeg ikke er det, kan jeg pakke hele scriptet ind i en async-funktion og gøre det samme. Dog er målet at kunne få adgang til et resultat uden at bruge await ELLER bruge await i en asynkron kontekst, som kan tilgås og afventes fra en synkron kontekst.

Svar #1

Dette bør ikke være muligt. ECMAScript-specifikationen forbyder det.

Bemærk først, at ECMAScript-specifikationen taler om "jobs", når der henvises til asynkron kodeudførelse relateret til promises. For eksempel, ved Promise-objekter:

Et løfte p er opfyldt, hvis p.then(f, r) straks vil sætte et Job i kø for at kalde funktionen f.

Job kræves kun at blive udført, når kaldestakken er tom. For eksempel angiver det ved Jobs og Host Operations at sætte Jobs i kø:

Et Job er en abstrakt lukning uden parametre, der starter en ECMAScript-beregning når ingen anden ECMAScript-beregning i øjeblikket er i gang.

Deres implementeringer skal overholde følgende krav:

  • På et fremtidigt tidspunkt, når der ikke er nogen kørende eksekveringskontekst, og eksekveringskontekststakken er tom, skal implementeringen:

    • [...] Kald den abstrakte lukning

Du har peget på Nodes udocumenterede og forældede process._tickCallback()-metode, som klart overtræder specifikationen, da den tillader job(i) i promise-jobkøen at blive udført, mens der er en ikke-tom opkaldsstak.

At bruge sådanne konstruktioner er derfor ikke god praksis, da anden kode ikke længere kan stole på de ovennævnte principper for asynkron jobeksekvering.

Med hensyn til process._tickCallback() specifikt: hvis det bruges til at tillade et promise at kalde dets then callbacks, vil dette ikke "virke", når det pågældende promise afhænger af en anden asynkron API, som for eksempel setTimeout. For eksempel vil følgende kodeuddrag køre i en uendelig løkke, da timeout-jobbet aldrig bliver udført:

let result = 0;
new Promise(resolve => 
    setTimeout(resolve, 10)
).then(() => result = 1);

while(!result) process._tickCallback();

console.log("all done")

Asynkronitet

I JavaScript kører asynkron kode (må køre), når kaldestakken er tom. Der bør ikke gøres forsøg på at få det til at fungere anderledes. Programmøren bør fuldt ud omfavne dette mønster.

Svar #2

Jeg fandt npm-pakken deasync. I den er der en kode linje: process._tickCallback(). Jeg implementerede det som en funktion. Dette vil kun løse et løfte, der er blevet fuldstændigt opfyldt. For eksempel:

function halt(promise) {
    let result;
    promise.then(res=>result=res);
    while(!result) process._tickCallback();
    return result;
};

let ans = halt(Promise.resolve(123));
console.log(ans); //=> 123

Funktionen kan forsvinde når som helst, da den ikke er inkluderet i dokumentationen. Hvis løftet er afventende, vil while-løkken være uendelig.