Funkcja asynchroniczna z + =


63

let x = 0;

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

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

xZarejestrowane wartości to 1i 5. Moje pytanie brzmi: dlaczego wartość x 5drugiego dziennika?

Jeśli polecenie testjest wykonywane po x += 1(ponieważ jest to funkcja asynchroniczna), wówczas wartość x wynosi 1 do czasu testwykonania, więc x += await 5należy podać wartość x 6.


1
Musisz znać różnicę między await (x += 5) i x += await 5.
Singhi John

Odpowiedzi:


60

TL; DR: Ponieważ +=czyta xwcześniej, ale zapisuje go po zmianie, ze względu na awaitsłowo kluczowe w drugim operandzie (po prawej stronie).


asyncfunkcje działają synchronicznie, gdy wywoływane są do pierwszej awaitinstrukcji.

Jeśli więc usuniesz await, zachowuje się jak normalna funkcja (z wyjątkiem tego, że nadal zwraca obietnicę).

W takim przypadku, można uzyskać 5i 6w konsoli:

let x = 0;

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

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

Pierwszy awaitprzestaje działać synchronicznie, nawet jeśli jego argument jest dostępny synchronicznie, więc zostaną zwrócone następujące elementy 1i 6, zgodnie z oczekiwaniami:

let x = 0;

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

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

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

Twoja sprawa jest jednak nieco bardziej skomplikowana.

Wstawiłeś awaitwyrażenie, które używa +=.

Prawdopodobnie wiesz, że w JS x += yjest identyczny x = (x + y). Użyję tego drugiego formularza dla lepszego zrozumienia:

let x = 0;

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

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

Kiedy tłumacz osiągnie tę linię ...

x = (x + await 5);

... zaczyna to oceniać i przechodzi w ...

x = (0 + await 5);

... wtedy osiąga awaiti zatrzymuje się.

Kod po wywołaniu funkcji zaczyna działać, modyfikuje wartość x, a następnie rejestruje ją.

xjest teraz 1.

Następnie, po zakończeniu skryptu głównego, interpreter wraca do wstrzymanej testfunkcji i kontynuuje ocenę tej linii:

x = (0 + 5);

A ponieważ wartość xjest już podstawiona, pozostaje 0.

Wreszcie, interpreter robi dodatek, przechowuje 5się xi rejestruje go.

Możesz sprawdzić to zachowanie, logując się w obiekcie pobierającym / ustawiającym właściwości obiektu (w tym przykładzie y.zodzwierciedla wartość 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;
}


Być może warto zauważyć: „Prawdopodobnie wiesz, to x += yjest identyczne z x = (x + y)”. - Nie dzieje się tak w każdej sytuacji w każdym języku, ale ogólnie można liczyć na to, że zachowają się tak samo.
Nick

11

Twoje oświadczenie x += await 5schodzi do

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

Wartość _temppoczątkowa to 0, a jeśli zmienisz się xpodczas await(co robi twój kod), to nie ma znaczenia, zostanie ona 5później przypisana .


9

Kod ten jest dość skomplikowany, ponieważ wymaga nieoczekiwanych skoków asynchronicznych tam i z powrotem. Przyjrzyjmy się (blisko), jak to będzie faktycznie wykonane, a potem wyjaśnię, dlaczego. Zmieniłem również dzienniki konsoli, aby dodać liczbę - ułatwia to odwoływanie się do nich, a także pokazuje lepiej, co jest rejestrowane:

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

Tak więc kod nie jest właściwie prosty, to na pewno. I mamy też dziwną 4/7rzecz. I to jest naprawdę cały problem tutaj.

Po pierwsze, wyjaśnijmy - funkcje asynchroniczne nietak naprawdę ściśle asynchroniczne. Zatrzymaliby wykonanie i wznowiliby później, jeśli awaitzostanie użyte słowo kluczowe. Bez niego wykonują synchronicznie od góry do dołu, wyrażenie po wyrażeniu synchronicznie:

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

Pierwszą rzeczą, którą musimy wiedzieć, że użycie awaitspowoduje, że reszta funkcji zostanie wykonana później. W podanym przykładzie oznacza to, że console.log('x1 :', x)zostanie wykonane po reszcie kodu synchronicznego. Jest tak, ponieważ wszelkie obietnice zostaną rozpatrzone po zakończeniu bieżącej pętli zdarzeń.

To wyjaśnia, dlaczego najpierw jesteśmy x2 : 1logowani i dlaczego logujemy się na drugim, ale nie dlaczego ta ostatnia wartość jest . Logicznie powinno być ... ale tutaj jest drugi haczyk do słowa kluczowego - wstrzyma wykonanie funkcji, ale wszystko, zanim jeszcze się uruchomi. będzie przetwarzany w następujący sposóbx2 : 55x += await 55awaitx += await 5

  1. Pobierz wartość x. To w momencie egzekucji 0.
  2. awaitnastępne wyrażenie, które jest 5. Tak więc funkcja zatrzymuje się teraz i zostanie wznowiona później.
  3. Wznów funkcję. Wyrażenie jest rozwiązywane jako 5.
  4. Dodaj wartość z 1. i wyrażenie z 2/3: 0 + 5
  5. Przypisz wartość od 4. do x

Tak, funkcja pauzy po niej przeczytać, że xjest 0i wznawia, gdy jest już zmieniona, jednakże nie ponownie odczytać wartość x.

Gdybyśmy rozpakować awaitdo Promiseekwiwalentu, które wykonać, trzeba:

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

Tak, to trochę skomplikowane, tak naprawdę dzieje się to, że obie operacje dodawania odbywają się parellaly, więc operacja wyglądałaby następująco:

W ramach obietnicy: x += await 5==> x = x + await 5==> x = 0 + await 5==>5

Na zewnątrz: x += 1==> x = x + 1==> x = 0 + 1==>1

ponieważ wszystkie powyższe operacje odbywają się od lewej do prawej, pierwszą część dodawania można obliczyć w tym samym czasie, a ponieważ przed 5 jest oczekiwanie, dodanie może nieco się opóźnić. Wykonanie można zobaczyć, umieszczając punkt przerwania w kodzie.


1

Async i Await są przedłużeniami obietnic. Funkcja asynchroniczna może zawierać wyrażenie oczekujące, które wstrzymuje wykonanie funkcji asynchronicznej i czeka na przekazaną rozdzielczość Obietnicy, a następnie wznawia wykonywanie funkcji asynchronicznej i zwraca rozstrzygniętą wartość. Pamiętaj, że słowo kluczowe oczekujące jest ważne tylko w funkcjach asynchronicznych.

Nawet jeśli zmieniłeś wartość x po wywołaniu funkcji testowej, nadal wartość x pozostanie równa 0, ponieważ funkcja asynchroniczna już utworzyła nową instancję. Oznacza to, że wszystko zmienia się na zmiennej poza nią, nie zmieni wartości wewnątrz niej po jej wywołaniu. Chyba że ustawisz swój przyrost powyżej funkcji testowej.


Oznacza to, że wszystko, co zmienia się na zmiennej poza nią, nie zmieni wartości wewnątrz niej po jej wywołaniu ”: to nieprawda. Funkcje asynchroniczne zrobić otrzymać zmienne zmian w trakcie ich wykonywania. Spróbuj tego: 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)Wyprowadza Received synchronous changeiReceived change
FZs
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.