Асинхронная функция с + =


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 равно 1 к моменту времени test, поэтому x += await 5должно быть значение x 6.


1
Вы должны знать разницу между await (x += 5) и x += await 5.
Сингхи Джон

Ответы:


60

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


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

Таким образом, если вы удалите awaitего, он будет вести себя как обычная функция (за исключением того, что он по-прежнему возвращает Promise).

В таком случае вы получаете 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и регистрирует его.

Вы можете проверить это поведение, зарегистрировавшись в свойствах объекта getter / setter (в этом примере 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 ожидается ожидание, эта добавка может немного задержаться. Вы можете увидеть выполнение, поместив точку останова в коде.


1

Async и Await являются продолжением обещаний. Асинхронная функция может содержать выражение await, которое приостанавливает выполнение асинхронной функции и ожидает разрешения переданной Promise, а затем возобновляет выполнение асинхронной функции и возвращает разрешенное значение. Помните, ключевое слово await допустимо только внутри асинхронных функций.

Даже если вы изменили значение x после вызова тестовой функции, значение x останется равным 0, поскольку асинхронная функция уже создала свой новый экземпляр. То есть все переменные, находящиеся вне ее, не изменят значение внутри нее после ее вызова. Если только вы не поместите свой шаг выше тестовой функции.


« То, что все меняется в переменной вне ее, не изменит значение внутри нее после того, как она была вызвана »: это не так. Функции 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
ФЗ
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.