Назначение оператора, карта и обещания. Что не так с этим кодом? Javascript - PullRequest
5 голосов
/ 07 марта 2020

Я делал кое-что и столкнулся с проблемой, которую не могу понять. Я упростил код, чтобы получить это:

function somePromise() {
    return new Promise((resolve, reject) => {
        resolve(1);
    });
}

async function main() {
    let count = 0;
    const arr = [1, 2, 3, 4, 5];
    const promises = arr.map(async () => {
        count += await somePromise();
    })
    await Promise.all(promises);
    console.log(count);
}

main().then(() => console.log('Done'));

Какой результат вы ожидаете?

1
Готово

зарегистрировано.

Когда я меняю

count += await somePromise();

на

const nb = await somePromise();
count += nb;

Я получаю

5
Готово

что я ожидал в первый раз.

Можете ли вы помочь мне найти, что не так? Я не понимаю.

1 Ответ

5 голосов
/ 07 марта 2020

Когда переводчик сталкивается с await, он приостанавливает функцию до разрешения Обещания. Даже если обещание выполнится немедленно, функция возобновит работу только в течение следующей микрозадачи. Напротив, массив итерируется через немедленно , синхронно. Когда вы делаете

const promises = arr.map(async () => {
    count += await somePromise();
})

После того, как массив итерирован, но до разрешения await s, "текущее" значение count, которое принимается для использования += извлекается до , await разрешается - и значение count до этого равно 0. Таким образом, интерпретатор выглядит так, как будто есть 5 отдельных операторов:

count += await somePromise();
count += await somePromise();
count += await somePromise();
count += await somePromise();
count += await somePromise();

, которые разрешают что-то вроде

const currentValueOfCount = count;
count = currentValueOfCount + await somePromise();
count = currentValueOfCount + await somePromise();
count = currentValueOfCount + await somePromise();
count = currentValueOfCount + await somePromise();
count = currentValueOfCount + await somePromise();

Так, каждый раз, правая часть = разрешается в 0 + 1, поэтому в конце l oop , count равно только 1.

Если вам интересно, где это описано в спецификации, посмотрите семантику для операторов присваивания . Где += является одним из AssignmentOperator с, следующий синтаксис:

LeftHandSideExpression AssignmentOperator AssignmentExpression

делает:

  1. Пусть lref будет результатом вычисления LeftHandSideExpression.
  2. Пусть lval будет? GetValue (lref).
  3. Пусть rref будет результатом вычисления AssignmentExpression.
  4. Пусть rval будет? GetValue (rref).
  5. Пусть op будет @, где AssignmentOperator равен @ =.
  6. Пусть r будет результатом применения op к lval и rval, как если бы вычислялось выражение lval op rval.

Посмотрите, как lval извлекается немедленно , прежде чем вычисляется правая часть оператора. (Если бы lval было извлечено после оценки правой стороны, AssignmentExpression, результаты были бы 5, как вы ожидаете)

Вот пример такого поведения без асинхронных операций:

let num = 5;
const fn = () => {
  num += 3;
  return 0;
}
num += 2 + fn();
console.log(num);

Выше num += 2 + fn(); немедленно извлекает num как 5 для использования в +=, затем вызывает fn(). Хотя num переназначается внутри fn, это не имеет никакого эффекта, потому что значение num уже было получено внешним +=.


с вашим рабочим кодом , когда вы делаете

const nb = await somePromise();
count += nb;

Это поместит значение разрешения somePromise в переменную nb, и , тогда count += nb; запустится. Это ведет себя как ожидалось, потому что «текущее» значение count, используемое для +=, извлекается после Обещание разрешается, поэтому, если предыдущая итерация переназначена count, оно будет успешно учтено к следующей итерации.

...