Función asíncrona con + =

63

let x = 0;

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

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

Los valores de xlogueado son 1y 5. Mi pregunta es: ¿por qué es el valor de x 5en el segundo registro?

Si testse ejecuta después x += 1(dado que es una función asíncrona), el valor de x es 1 en el momento en que testse ejecuta, por lo que x += await 5debe ser el valor de x 6.

ALDRIN P VINCENT
fuente
1
Debes saber la diferencia entre await (x += 5) y x += await 5.
Singhi John

Respuestas:

60

TL; DR: Porque +=lee xantes, pero lo escribe después de que ha cambiado, debido a la awaitpalabra clave en su segundo operando (lado derecho).


asyncLas funciones se ejecutan sincrónicamente cuando llaman hasta la primera awaitinstrucción.

Entonces, si elimina await, se comporta como una función normal (con la excepción de que aún devuelve una Promesa).

En ese caso, obtienes 5y 6en la consola:

let x = 0;

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

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

El primero awaitdetiene la ejecución síncrona, incluso si su argumento está disponible sincrónicamente, por lo que se devolverá lo siguiente 1y 6, como es de esperar:

let x = 0;

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

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

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

Sin embargo, su caso es un poco más complicado.

Has puesto awaitdentro de una expresión, que usa +=.

Probablemente sepa que en JS x += yes idéntico a x = (x + y). Usaré la última forma para una mejor comprensión:

let x = 0;

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

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

Cuando el intérprete llega a esta línea ...

x = (x + await 5);

... comienza a evaluarlo, y se convierte en ...

x = (0 + await 5);

... entonces, llega al awaity se detiene.

El código después de la llamada a la función comienza a ejecutarse y modifica el valor de x, luego lo registra.

xes ahora 1.

Luego, una vez que sale el guión principal, el intérprete vuelve a la testfunción en pausa y continúa evaluando esa línea:

x = (0 + 5);

Y, dado que el valor de xya está sustituido, permanece 0.

Por último, el intérprete hace la adición, tiendas 5de x, y lo registra.

Puede verificar este comportamiento iniciando sesión dentro de un objeto getter / setter (en este ejemplo, y.zrefleja el valor de 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;
}

FZs
fuente
Quizás valga la pena señalar: "Probablemente lo sepas, eso x += yes idéntico a x = (x + y)". - Este no es el caso en todas las situaciones en todos los idiomas, pero en general puede contar con que actúen de la misma manera.
Nick
11

Su declaración se x += await 5desugará a

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

El _tempvalor orario es 0, y si cambia xdurante await(lo que hace su código) no importa, se asigna 5después.

Bergi
fuente
9

Este código es bastante complejo de seguir porque requiere algunos saltos asíncronos inesperados de un lado a otro. Examinemos (cerca de) cómo se ejecutará realmente y luego explicaré por qué. También he cambiado los registros de la consola para agregar un número, lo que facilita la referencia a ellos y también muestra mejor lo que se registra:

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

Por lo tanto, el código en realidad no funciona de manera directa, eso es seguro. Y también tenemos 4/7algo extraño . Y eso es realmente la totalidad del problema aquí.

En primer lugar, aclaremos: las funciones asincrónicas no son realmente estrictamente asincrónicas. Solo pausarían la ejecución y reanudarían más tarde si awaitse usa la palabra clave. Sin ella, ejecutan de arriba a abajo, expresión tras expresión sincrónicamente:

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");

Entonces, lo primero que necesitamos saber es que usar awaithará que el resto de la función se ejecute más tarde. En el ejemplo dado, eso significa que console.log('x1 :', x)se ejecutará después del resto del código síncrono. Esto se debe a que cualquier Promesa se resolverá una vez que finalice el ciclo del evento actual.

Entonces, esto explica por qué nos x2 : 1registramos primero y por qué x2 : 5se registra en segundo lugar, pero no por qué es el último valor 5. Lógicamente x += await 5debería ser 5... pero aquí está la segunda captura de la awaitpalabra clave: pausará la ejecución de la función, pero cualquier cosa antes de que ya se haya ejecutado. x += await 5en realidad se va a procesar de la siguiente manera

  1. Obtener el valor de x. En el momento de la ejecución, eso es 0.
  2. awaitLa siguiente expresión que es 5. Por lo tanto, la función se detiene ahora y se reanudará más adelante.
  3. Reanudar la función. La expresión se resuelve como 5.
  4. Agregue el valor de 1. y la expresión de 2/3: 0 + 5
  5. Asigne el valor de 4. a x

Por lo tanto, las pausas función después de que leyeron que xes 0y se reanuda cuando ya ha cambiado, sin embargo, no volver a leer el valor de x.

Si desenvolvemos el equivalente awaiten el Promiseequivalente que se ejecutaría, tiene:

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

VLAZ
fuente
3

Sí, es un poco complicado lo que realmente está sucediendo, ambas operaciones de suma están sucediendo de manera paralela, por lo que la operación sería como:

Dentro de la promesa: x += await 5==> x = x + await 5==> x = 0 + await 5==>5

Afuera: x += 1==> x = x + 1==> x = 0 + 1==>1

Como todas las operaciones anteriores se realizan de izquierda a derecha, la primera parte de la suma puede calcularse al mismo tiempo y dado que hay una espera antes de las 5, esa adición puede retrasarse un poco. Puede ver la ejecución colocando un punto de interrupción dentro del código.

Pranav C Balan
fuente
1

Async y Await son extensiones de promesas. Una función asincrónica puede contener una expresión de espera que detiene la ejecución de la función asincrónica y espera la resolución de la Promesa aprobada, y luego reanuda la ejecución de la función asincrónica y devuelve el valor resuelto. Recuerde, la palabra clave await solo es válida dentro de las funciones asíncronas.

Incluso si ha cambiado el valor de x después de llamar a la función de prueba, el valor de x seguirá siendo 0 porque la función asincrónica ya ha creado su nueva instancia. Lo que significa que todo cambia en la variable fuera de ella no cambiará el valor dentro de ella después de que se llamó. A menos que ponga su incremento por encima de la función de prueba.

Qonvex620
fuente
"Lo que significa que todo cambia en la variable fuera de ella no cambiará el valor dentro de ella después de que se llamó ": eso no es cierto. Funciones asíncronas hacer reciben cambios de variables durante su ejecución. Intenta esto: 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)Genera Received synchronous changeyReceived change
ZF