Função assíncrona com + =


63

let x = 0;

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

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

Os valores de xlogado são 1e 5. Minha pergunta é: por que o valor do x 5segundo log?

Se o testé executado depois x += 1(já que é uma função assíncrona), o valor de x é 1 no momento em que testé executado, portanto x += await 5deve ser o valor de x 6.


11
Você deve saber a diferença entre await (x += 5) e x += await 5.
Singhi John

Respostas:


60

TL; DR: Porque +=xantes, mas grava após alterar, devido à awaitpalavra - chave em seu segundo operando (lado direito).


asyncAs funções são executadas de forma síncrona quando foram chamadas até a primeira awaitinstrução.

Portanto, se você remover await, ele se comportará como uma função normal (com a exceção de que ainda retorna uma promessa).

Nesse caso, você obtém 5e 6no console:

let x = 0;

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

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

A primeira awaitinterrompe a execução síncrona, mesmo que seu argumento esteja disponível de forma síncrona; portanto, o seguinte retornará 1e 6, conforme o esperado:

let x = 0;

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

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

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

No entanto, seu caso é um pouco mais complicado.

Você colocou awaitdentro de uma expressão que usa +=.

Você provavelmente sabe que em JS x += yé idêntico a x = (x + y). Usarei o último formulário para entender melhor:

let x = 0;

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

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

Quando o intérprete chega a esta linha ...

x = (x + await 5);

... começa a avaliar e passa a ...

x = (0 + await 5);

... então, chega ao awaite para.

O código após a chamada da função começa a ser executado, modifica o valor de xe registra-o.

xé agora 1.

Depois que o script principal é encerrado, o intérprete retorna à testfunção em pausa e continua avaliando essa linha:

x = (0 + 5);

E, como o valor de xjá está substituído, ele permanece 0.

Finalmente, o intérprete faz a adição, lojas 5para x, e registra-lo.

Você pode verificar esse comportamento efetuando login em um getter / setter de propriedade de objeto (neste exemplo, y.zreflete o 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;
}


Talvez vale a pena notar: "Você provavelmente sabe, isso x += yé idêntico a x = (x + y)". - Este não é o caso em todas as situações em todos os idiomas, mas em geral você pode contar com eles agindo da mesma maneira.
Nick

11

Sua declaração x += await 5deseja que

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

O _tempvalor do orary é 0e, se você alterar xdurante o await(que é o seu código), não importa, ele será atribuído 5posteriormente.


9

Esse código é bastante complexo a seguir, pois leva alguns saltos assíncronos inesperados para frente e para trás. Vamos examinar (perto de) como ele realmente será executado e explicarei o porquê posteriormente. Também alterei os logs do console para adicionar um número - facilita a referência a eles e também mostra melhor o que é registrado:

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

Portanto, o código não está indo exatamente de maneira direta, com certeza. E nós temos uma 4/7coisa estranha também. E isso é realmente a totalidade do problema aqui.

Antes de tudo, vamos esclarecer - as funções assíncronas não são estritamente assíncronas. Eles apenas pausariam a execução e continuariam mais tarde se a awaitpalavra-chave fosse usada. Sem ele, eles executam de cima para baixo, expressão após expressão de forma síncrona:

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

Portanto, a primeira coisa que precisamos saber é que o uso awaitfará com que o restante da função seja executado posteriormente. No exemplo dado, isso significa que console.log('x1 :', x)será executado após o restante do código síncrono. Isso ocorre porque qualquer promessa será resolvida após a conclusão do loop de eventos atual.

Portanto, isso explica por que somos x2 : 1registrados primeiro e por que x2 : 5é registrado depois, mas não por que o último valor é 5. Logicamente x += await 5deveria ser 5... mas aqui está a segunda captura da awaitpalavra-chave - ela fará uma pausa na execução da função, mas qualquer coisa antes que ela já tenha sido executada. x += await 5será realmente processado da seguinte maneira

  1. Busque o valor de x. No momento da execução, é isso 0.
  2. awaita próxima expressão que é 5. Portanto, a função pausa agora e será retomada mais tarde.
  3. Continue a função. Expressão é resolvida como 5.
  4. Adicione o valor de 1. e a expressão de 2/3: 0 + 5
  5. Atribua o valor de 4. a x

Assim, as pausas função, depois de ler que xé 0e recomeça quando ele já mudou, no entanto, ele não re-ler o valor x.

Se desembrulhar o awaitno Promiseequivalente que iria executar, você tem:

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

É um pouco complicado o que realmente está acontecendo: as duas operações de adição estão acontecendo paralelamente, portanto a operação seria como:

Dentro da promessa: x += await 5==> x = x + await 5==> x = 0 + await 5==>5

Fora: x += 1==> x = x + 1==> x = 0 + 1==>1

uma vez que todas as operações acima estão acontecendo da esquerda para a direita, a primeira parte da adição pode ser calculada ao mesmo tempo e, como há um período de espera antes de 5, a adição pode demorar um pouco. Você pode ver a execução colocando o ponto de interrupção no código.


1

Async e Await são extensões de promessas. Uma função assíncrona pode conter uma expressão de espera que interrompe a execução da função assíncrona e aguarda a resolução da Promessa aprovada e, em seguida, retoma a execução da função assíncrona e retorna o valor resolvido. Lembre-se de que a palavra-chave wait é válida apenas dentro de funções assíncronas.

Mesmo que você tenha alterado o valor de x após chamar a função de teste, o valor de x permanecerá 0, pois a função assíncrona já criou sua nova instância. Significando que tudo muda na variável fora dela não altera o valor dentro dela depois que foi chamado. A menos que você coloque seu incremento acima da função de teste.


" Significando que tudo muda na variável fora dela não mudará o valor dentro dela depois que foi chamado ": isso não é verdade. Funções assíncronas fazer receber alterações variáveis durante a sua execução. Apenas tente o seguinte: 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)ele gera Received synchronous changeeReceived change
FZs
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.