Asyn c функция с + = - PullRequest
       67

Asyn c функция с + =

63 голосов
/ 18 января 2020

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

Ответы [ 5 ]

60 голосов
/ 18 января 2020

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 и регистрирует it.

Вы можете проверить это поведение, войдя внутрь объекта 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;
}
11 голосов
/ 18 января 2020

Ваше утверждение x += await 5 десугарс к

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

Значение _temp является 0, и если вы измените x во время await (что делает ваш код), оно не не имеет значения, ему назначают 5 впоследствии.

9 голосов
/ 18 января 2020

Этот код довольно сложен для отслеживания, потому что требуется несколько неожиданных асинхронных c скачков назад и вперед Давайте рассмотрим (близко), как это будет на самом деле, и я объясню почему потом. Я также изменил журналы консоли, чтобы добавить число - это упрощает обращение к ним, а также показывает, что записывается:

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. И это действительно вся проблема здесь.

Прежде всего, давайте уточним - асин c функции не на самом деле строго асинхронные. Они приостановят выполнение и возобновят позже, если будет использовано ключевое слово 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) будет выполняться после остальной части синхронного кода. Это потому, что любые Обещания будут разрешены после завершения текущего события l oop.

Итак, это объясняет, почему мы получаем x2 : 1 logged first и почему x2 : 5 регистрируется second, но не то, почему последнее значение равно 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 голосов
/ 18 января 2020

Да, немного сложнее всего то, что на самом деле происходит, обе операции сложения выполняются пареллой, поэтому операция будет выглядеть так:

В пределах обещания: x += await 5 ==> x = x + await 5 ==> x = 0 + await 5 = => 5

Снаружи: x += 1 ==> x = x + 1 ==> x = 0 + 1 ==> 1

, поскольку все вышеуказанные операции выполняются слева направо Первая часть сложения может быть рассчитана в одно и то же время, и поскольку до 5 ожидается ожидание, эта добавка может немного задержаться. Вы можете увидеть выполнение, поместив точку останова в коде.

1 голос
/ 18 января 2020

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

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

...