Funzione asincrona con + =


63

let x = 0;

async function test() {
    x += await 5;
    console.log('x :', x);
}

test();
x += 1;
console.log('x :', x);

I valori di xlogged sono 1e 5. La mia domanda è: perché è il valore dix 5 sul secondo registro?

Se il testviene eseguito dopo x += 1(poiché è una funzione asincrona), il valore di x è 1 entro il momento in cui testviene eseguito, quindi x += await 5dovrebbe rendere il valore di x 6.


1
Devi conoscere la differenza tra await (x += 5) e x += await 5.
Singhi John,

Risposte:


60

TL; DR: Perché +=legge xprima, ma lo scrive dopo che è stato modificato, a causa della awaitparola chiave nel suo secondo operando (lato destro).


asyncle funzioni vengono eseguite in modo sincrono quando hanno chiamato fino alla prima awaitistruzione.

Quindi, se lo rimuovi await, si comporta come una normale funzione (con l'eccezione che restituisce comunque una Promessa).

In tal caso, ottieni 5e 6nella console:

let x = 0;

async function test() {
  x += 5;
  console.log('x :', x);
}

test();
x += 1;
console.log('x :', x);

Il primo awaitinterrompe l'esecuzione sincrona, anche se l'argomento è disponibile in modo sincrono, quindi verrà restituito quanto segue 1e 6, come previsto:

let x = 0;

async function test() {
  // Enter asynchrony
  await 0;

  x += 5;
  console.log('x :', x);
}

test();
x += 1;
console.log('x :', x);

Tuttavia, il tuo caso è un po 'più complicato.

Hai messo awaitdentro un'espressione che usa +=.

Probabilmente sai che in JS x += yè identico a x = (x + y). Userò quest'ultimo modulo per una migliore comprensione:

let x = 0;

async function test() {
  x = (x + await 5);
  console.log('x :', x);
}

test();
x += 1;
console.log('x :', x);

Quando l'interprete raggiunge questa linea ...

x = (x + await 5);

... inizia a valutarlo e si trasforma in ...

x = (0 + await 5);

... poi raggiunge awaite si ferma.

Il codice dopo la chiamata della funzione inizia a essere eseguito e modifica il valore di x, quindi lo registra.

xè adesso 1.

Quindi, una volta terminato lo script principale, l'interprete torna alla testfunzione in pausa e continua a valutare quella riga:

x = (0 + 5);

E, poiché il valore di xè già sostituito, rimane 0.

Infine, l'interprete fa l'aggiunta, i negozi 5a x, e lo registra.

È possibile verificare questo comportamento accedendo a un oggetto getter / setter della proprietà dell'oggetto (in questo esempio, y.zriflette il valore di x:

let x = 0;
const y = {
  get z() {
    console.log('get x :', x);
    return x;
  },
  set z(value) {
    console.log('set x =', value);
    x = value;
  }
};

async function test() {
  console.log('inside async function');
  y.z += await 5;
  console.log('x :', x);
}

test();
console.log('main script');
y.z += 1;
console.log('x :', x);

/* Output:

inside async function
get x : 0   <-- async fn reads
main script
get x : 0
set x = 1
x : 1
set x = 5   <-- async fn writes
x : 5       <-- async fn logs

*/
/* Just to make console fill the available space */
.as-console-wrapper {
  max-height: 100% !important;
}


Forse vale la pena notare: "Probabilmente sai, x += yè identico a x = (x + y)". - Questo non è il caso di ogni situazione in ogni lingua, ma in generale puoi contare sul fatto che agiscono allo stesso modo.
Nick

11

La tua dichiarazione x += await 5desugars a

const _temp = x;
await;
x = _temp + 5;

Il _tempvalore dell'orario è 0, e se cambi xdurante il await(che il tuo codice fa) non importa, viene assegnato in 5seguito.


9

Questo codice è abbastanza complesso da seguire perché richiede alcuni salti asincroni inaspettati avanti e indietro. Esaminiamo (vicino a) come verrà effettivamente eseguita e spiegheremo perché in seguito. Ho anche modificato i registri della console per aggiungere un numero: semplifica il riferimento ad essi e mostra meglio ciò che viene registrato:

let x = 0;                        // 1 declaring and assigning x

async function test() {           // 2 function declaration
    x += await 5;                 // 4/7 assigning x
    console.log('x1 :', x);       // 8 printing
}

test();                           // 3 invoking the function
x += 1;                           // 5 assigning x
console.log('x2 :', x);           // 6 printing

Quindi, il codice in realtà non sta andando in modo diretto, questo è certo. E abbiamo anche una 4/7cosa strana . E questo è davvero l'intero problema qui.

Prima di tutto, chiariamo: le funzioni asincrone non sono in realtà strettamente asincrone. Metterebbero in pausa l'esecuzione e riprenderanno più tardi se awaitsi utilizza la parola chiave. Senza di esso, eseguono dall'alto verso il basso, espressione dopo espressione in modo sincrono:

async function foo() {
  console.log("--one");
  console.log("--two");
}

console.log("start");
foo();
console.log("end");

async function foo() {
  console.log("--one");
  await 0; //just satisfy await with an expression
  console.log("--two");
}

console.log("start");
foo();
console.log("end");

Quindi, la prima cosa che dobbiamo sapere è che l'utilizzo awaitfarà eseguire il resto della funzione in un secondo momento. Nell'esempio dato, ciò significa checonsole.log('x1 :', x) verrà eseguito dopo il resto del codice sincrono. Questo perché eventuali promesse verranno risolte al termine dell'attuale ciclo di eventi.

Quindi, questo spiega perché siamo x2 : 1registrati per primi e perché x2 : 5è registrato per secondo, ma non perché sia ​​l'ultimo valore 5. Logicamente x += await 5dovrebbe essere 5... ma ecco il secondo awaitproblema per la parola chiave: metterà in pausa l'esecuzione della funzione ma qualsiasi cosa prima che sia già stata eseguita.x += await 5verrà effettivamente elaborato nel modo seguente

  1. Recupera il valore di x. Al momento dell'esecuzione, questo è0 .
  2. await la prossima espressione che è 5 . Quindi, la funzione si interrompe ora e verrà ripresa in seguito.
  3. Riprendi la funzione. L'espressione è risolta come 5.
  4. Aggiungi il valore da 1. e l'espressione da 2/3: 0 + 5
  5. Assegna il valore da 4. a x

Quindi, la funzione si interrompe dopo aver letto che xè 0e riprende quando è già cambiata, tuttavia, non rilegge il valore dix .

Se noi scartare i awaitin Promiseequivalente che avrebbe eseguito, si dispone:

let x = 0;                        // 1 declaring and assigning x

async function test() {           // 2 function declaration
    const temp = x;               // 4 value read of x
    await 0; //fake await to pause for demo
    return new Promise((resolve) => {
      x = temp + 5;               // 7 assign to x
      console.log('x1 :', x);     // 8 printing
      resolve();
    });
}

test();                           // 3 invoking the function
x += 1;                           // 5 assigning x
console.log('x2 :', x);           // 6 printing


3

Ya è un po 'complicato quello che sta realmente accadendo è che entrambe le operazioni di addizione stanno avvenendo in modo parellurale, quindi l'operazione sarebbe come:

All'interno della promessa: x += await 5==> x = x + await 5==> x = 0 + await 5==>5

Esterno: x += 1==> x = x + 1==> x = 0 + 1==>1

poiché tutte le operazioni sopra descritte avvengono da sinistra a destra, la prima parte dell'aggiunta può essere calcolata contemporaneamente e poiché è in attesa di 5 prima che l'aggiunta possa ritardare un po '. Puoi vedere l'esecuzione inserendo il punto di interruzione nel codice.


1

Async e Await sono estensioni delle promesse. Una funzione asincrona può contenere un'espressione wait che mette in pausa l'esecuzione della funzione asincrona e attende la risoluzione della Promessa passata, quindi riprende l'esecuzione della funzione asincrona e restituisce il valore risolto. Ricorda, la parola chiave wait è valida solo all'interno delle funzioni asincrone.

Anche se hai cambiato il valore di x dopo aver chiamato la funzione di test, il valore di x rimarrà 0 perché la funzione asincrona ha già creato la sua nuova istanza. Significa che tutto cambia sulla variabile al di fuori di essa non cambierà il valore al suo interno dopo che è stato chiamato. A meno che non si metta l'incremento sopra la funzione di test.


" Significa che tutto cambia sulla variabile al di fuori di essa non cambierà il valore al suo interno dopo che è stato chiamato ": non è vero. Funzioni asincrone fare ricevono variabile cambia durante la loro esecuzione. Prova questo: let x="Didn't receive change"; (async()=>{await 'Nothing'; console.log(x); await new Promise(resolve=>setTimeout(resolve,2000)); console.log(x)})(); x='Received synchronous change'; setTimeout(()=>{x='Received change'},1000)emette Received synchronous changeeReceived change
FZ il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.