Функція асинхронізації з + =


63

let x = 0;

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

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

Значення xзареєстрованих є 1та 5. Моє запитання: чому значення x 5другого журналу?

Якщо testфункція виконується після x += 1(оскільки вона є функцією асинхронізації), то значення x дорівнює часу testдорівнює 1, тому x += await 5має бути значенням x 6.


1
Ви повинні знати різницю між await (x += 5) і x += await 5.
Сінгхі Іван

Відповіді:


60

TL; DR: Тому що +=читає xраніше, але записує його після зміни, через awaitключове слово у другому операнді (справа).


asyncфункції запускаються синхронно, коли вони викликали до першого awaitоператора.

Отже, якщо ви видалите await, він поводиться як нормальна функція (за винятком того, що він все одно повертає Обіцянку).

У такому випадку ви отримуєте 5і 6в консолі:

let x = 0;

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

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

Перший awaitзупиняє синхронний запуск, навіть якщо його аргумент доступний синхронно, тому наступне повернеться 1і 6, як ви очікуєте:

let x = 0;

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

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

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

Однак ваш випадок трохи складніший.

Ви помістили awaitвсередину вираз, який використовує +=.

Ви, мабуть, знаєте, що в JS x += yтотожне x = (x + y). Я використаю останню форму для кращого розуміння:

let x = 0;

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

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

Коли перекладач досягне цієї лінії ...

x = (x + await 5);

... він починає його оцінювати, і переходить до ...

x = (0 + await 5);

... тоді вона досягає awaitі зупиняється.

Код після виклику функції починає виконуватись та змінює значення x, потім реєструє його.

xє зараз 1.

Потім, після виходу основного сценарію, інтерпретатор повертається до функції призупинення testі продовжує оцінювати цей рядок:

x = (0 + 5);

І, оскільки значення xвже заміщене, воно залишається 0.

Нарешті, перекладач робить додавання, зберігає 5його xта записує в журнал.

Ви можете перевірити цю поведінку, увійшовши всередину об’єкта, що отримує / налаштовує властивості (у цьому прикладі y.zвідображається значення 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;
}


Можливо, варто відзначити: "Ви, мабуть, знаєте, що x += yтотожне x = (x + y)". - Це не так у всіх ситуаціях на кожній мові, але загалом ви можете розраховувати, що вони діятимуть однаково.
Нік

11

Твоя заява x += await 5не відповідає

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

_tempOrary значення 0, і якщо змінити xпід час await(що робить ваш код) це не має значення, він отримує призначення 5згодом.


9

Цей код досить складний для дотримання, оскільки він потребує несподіваних асинхронних стрибків вперед і назад. Давайте вивчимо (близько), як це насправді буде виконано, і я поясню, чому потім. Я також змінив журнали консолі, щоб додати число - полегшує посилання на них, а також краще показує, що реєструється:

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

Отже, код насправді не йде прямо, це точно. І у нас теж є дивна 4/7річ. І це справді вся сутність проблеми.

Перш за все, уточнимо - функції асинхрони насправді не є суто асинхронними. Вони призупиняють виконання та відновлять пізніше, якщо використовується awaitключове слово. Без цього вони виконують зверху вниз, вираз після виразу синхронно:

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

Отже, перше, що нам потрібно знати, що використання awaitзмусить решту функції виконати пізніше. У наведеному прикладі це означає, що console.log('x1 :', x)виконується після решти синхронного коду. Це тому, що будь-які Обіцянки будуть вирішені після завершення поточного циклу подій.

Отже, це пояснює, чому ми x2 : 1реєструємось першими і чому x2 : 5реєструємо друге, але не чому саме це значення 5. Логічно x += await 5повинно бути 5... але ось другий підхоплення до awaitключового слова - воно призупинить виконання функції, але все, що завгодно, до того, як воно вже запущене. x += await 5насправді буде оброблено наступним чином

  1. Отримати значення x. На момент страти, це 0.
  2. awaitнаступний вираз, який є 5. Отже, функція призупиняється зараз і буде відновлена ​​пізніше.
  3. Відновіть функцію. Вираз вирішується як 5.
  4. Додайте значення з 1. та вираз від 2/3: 0 + 5
  5. Призначте значення від 4. до x

Таким чином, функція паузи після нього прочитав , що xце 0і поновлюється , коли вона вже змінилася, однак, він не перечитував значення x.

Якщо розгортати awaitв Promiseеквіваленті , який буде виконувати, у вас є:

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

Я його трохи хитро, що насправді відбувається, це те, що операції додавання відбуваються паралельно, тому операція виглядатиме так:

У межах обіцянки: x += await 5==> x = x + await 5==> x = 0 + await 5==>5

Зовні: x += 1==> x = x + 1==> x = 0 + 1==>1

оскільки всі вищезазначені операції відбуваються зліва направо, то перша частина додавання може бути обчислена одночасно і оскільки існує очікування до 5, то Additio може трохи затриматись. Ви можете побачити виконання, поставивши в коді точку розриву.


1

Async і Await - це продовження обіцянок. Функція асинхронізації може містити вираз очікування, який призупиняє виконання функції асинхронізації та чекає прийнятої резолюції Promise, а потім відновлює виконання функції асинхронізації та повертає розв'язане значення. Пам'ятайте, що ключове слово, яке очікує, дійсне лише у функціях асинхронізації.

Навіть якщо ви змінили значення x після виклику тестової функції, все-таки значення x залишиться 0, оскільки функція async вже створила це новий екземпляр. Тобто все, що змінюється на змінній поза нею, не змінить значення всередині неї після її виклику. Якщо тільки ви не зробите свій приріст вище тестової функції.


" Значить, все, що змінюється на змінній поза нею, не змінить значення всередині неї після її виклику ": це неправда. Функції Async роблять отримувати змінні зміни в процесі їх виконання. Спробуйте це: 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)він виводить Received synchronous changeіReceived change
FZs
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.