Почему эта функция asyn c выполняется перед эквивалентной цепочкой Promise.then, определенной перед ней? - PullRequest
17 голосов
/ 16 июня 2020

У меня такой код:

var incr = num => new Promise(resolve => {
  resolve(num + 1);
});

var x = incr(3)
  .then(resp => incr(resp))
  .then(resp => console.log(resp));

async function incrTwice(num) {
  const first = await incr(num);
  const twice = await incr(first);
  console.log(twice);
}

incrTwice(6);

Что, как я считаю (возможно, ошибочно), показывает два эквивалентных способа достижения той же функциональности: во-первых, с помощью цепочки обещаний, а во-вторых, с помощью синтаксиса c async / await.

Я ожидал бы, что сначала будет выполнено решение для цепочки обещаний в console.log, затем - во второй - функция asyn c, однако сначала будет выведена функция asyn c console.log, а затем будет распечатано решение цепочки обещаний.

My logi c выглядит следующим образом:

  1. x s начальное разрешение будет первым в очереди микрозадач по мере обработки объявления
  2. стек пуст между объявление x и incrTwice, что приведет к очистке очереди микрозадач (что приведет к завершению цепочки обещаний)
    • x печатает первым
  3. incrTwice определено
  4. incrTwice выполняет постановку в очередь микрозадач на await с, в конечном итоге печать на консоль
    • incrTwice печатает второй

Очевидно, что у меня где-то недоразумение, может ли кто-нибудь указать, в чем я ошибаюсь?

Ответы [ 2 ]

12 голосов
/ 16 июня 2020

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

Примеры игрушек, которые используют только обещания с немедленным разрешением делают этот порядок зависимым от семантики очередей микрозадач, а не от фактических асинхронных задач, что делает его чисто академическим c упражнением (результат которого может быть изменен в spe c) *. 1007 *

В любом случае, давайте проясним ваше недоразумение:

между объявлением x и incrTwice стек пуст, что приведет к очистке очереди микрозадач

Нет, стек становится пустым только после того, как весь код пользователя будет выполнен до завершения. В стеке все еще есть глобальный контекст выполнения элемента <script>. Никакие микрозадачи не выполняются до тех пор, пока не будет завершен весь синхронный код (incr = …, x = incr(3).… и incrTwice(6)).

Я считаю, что [код] показывает два эквивалентных способа достижения той же функциональности: первый путем объединения обещаний и второго с синтаксисом c сахаром async / await.

Не совсем так. У цепочки .then() есть дополнительный шаг разрешения при отмене вложенности обещания incr(resp), возвращаемого первым обработчиком .then(…). Чтобы заставить его вести себя точно так же, как обещания await ed в incrTwice, вам нужно будет написать

incr(3).then(resp =>
  incr(resp).then(resp =>
    console.log(resp)
  )
);

. Если вы это сделаете, вы фактически получите console логи в порядок, в котором вы запустили две цепочки обещаний, потому что они будут выполнять одинаковое количество микрозадач, пока не будет выполнена console.log().

Подробнее см. Каков порядок выполнения в javascript promises , Порядок выполнения обещаний внутри обещаний , Что происходит, когда мы возвращаем значение и когда мы возвращаем Promise.resolve из цепочки then () в очереди микрозадач? , В чем разница между возвращаемыми обещаниями? , Порядок выполнения обещаний ES6 для возвращаемых значений

1 голос
/ 16 июня 2020

Ваше мышление понятно и логично. Причина наблюдаемого поведения связана с одной из гарантий, которые были встроены в Promise API, а именно, что обещания всегда выполняются асинхронно , даже если они выполняют синхронные операции (например, немедленное разрешение обещания ). С технической точки зрения это означает, что обратный вызов обещания никогда не будет вызван до тех пор, пока не будет завершено текущий запуск .

Как указано в MDN :

Обратные вызовы никогда не будут вызываться до завершения текущего запуска события JavaScript l oop.

Итак:

Promise.resolve(10).then(value => console.log(value));
console.log(20); //20, 10 - NOT 10, 20

Я расскажу об этом в своем руководстве по обещаниям, которое можно найти здесь .

...