Правильная ментальная модель для «ожидающего» асинхронной функции Javascript: генератор «доходность» против «обещание.then ()»? - PullRequest
2 голосов
/ 04 июля 2019

Какой из урожайности генератора против обещания .then () является более * правильной ментальной моделью для понимания 'await'?

Сравнение свойств, полученное путем пошагового выполнения фрагмента ниже с помощью отладчика:

ждут:

  1. ожидание не приостанавливает / приостанавливает выполнение запущенной асинхронной функции. (Запущенная асинхронная функция «выполняется до завершения», возвращающая ожидающее обещание, когда интерпретатор достигает 1-го ожидания. Затем она немедленно удаляется из стека вызовов.)

  2. ждут, пока обещание не уляжется.

  3. await expression оборачивает оставшуюся часть кода функции в микрозадачу.

* * Генератор тысяча двадцать-одина-выход:
  1. yield приостанавливает выполнение запущенной функции. функции генератора не «работают до завершения».
  2. yield promise обеспечивает выполнение promise до выполнения оставшегося кода.
  3. yield не переносит и не создает микрозадачу.

promise.then (обратный вызов):

  1. не приостанавливает выполнение запущенной функции.
  2. ждет выполнения обещания перед выполнением обратного вызова.
  3. создает микрозадачу (обратный вызов)

//promise returning function
function foo(whoCalled) {
   let p = new Promise(function(resolve, reject) { 
     setTimeout( () => {
       console.log('resolving from setTimeout - called by: ' + whoCalled)
       resolve('resolve value') }, .1)
   })
   return p
}

//async await
async function asyncFunc() {
  await foo('async function')
  //rest of running function’s code…
  console.log('async function howdy')
}

//generator yield:
function* gen() {
   yield foo('generator function')
   //rest of running function’s code…
   console.log('generator function howdy')
}

//promise.then():
function thenFunc() {
   let r = foo('promise.then function').then(() => {
       //rest of running function’s code…
       console.log('promise.then() howdy')
   })
   return r
}

//main
function main() {

  //async await
  var a = asyncFunc() 
  console.log(a) //logs Promise { <pending> }
                 //the rest of the code following await foo() runs as a microtask runs once foo() resolves. The call stack was cleared.

  //generator
   var g = gen()
   console.log(g) // logs Object [Generator] {}
   var p = g.next().value
   console.log(p) //logs Promise { <pending> }
   g.next()       //the rest of the code following yield running gen function's code runs. call stack was not cleared.

   //promise.then()
   var x = thenFunc()
   console.log(x) //logs Promise { <pending> }
                   //the then(callback) microtask runs once foo() resolves. The call stack was cleared
}
main()
console.log('main is off the call stack - launch/startup macrotask completing. Event loop entering timer phase.')

И, выходя за рамки этого сравнения, какова точная ментальная модель того, что await делает под капотом?

ждите в последней спецификации ECMAScript для справки: https://www.ecma -international.org / ECMA-262 / 10,0 / index.html # ждут

ожидайте в исходном коде V8: https://github.com/v8/v8/blob/4b9b23521e6fd42373ebbcb20ebe03bf445494f9/src/builtins/builtins-async-function-gen.cc#L252

Ответы [ 3 ]

3 голосов
/ 04 июля 2019

Это не одно или другое.На самом деле они оба вместе: async / await = yield + then + бегун.

async function действительно приостанавливается ключевым словом await, как генератор function* приостанавливается по ключевому слову yield.Механизм того, как выполнение останавливается и возобновляется в середине операторов потока управления, точно такой же.

Отличается то, как управляются эти продолжения и что возвращают функции.Функция генератора создает объект генератора при вызове, и вам необходимо явно вызвать метод next() извне, чтобы выполнить код yield на yield.С другой стороны, асинхронная функция создает обещание и сама управляет выполнением.Он не ожидает внешних вызовов next(), а выполняет каждый асинхронный шаг как можно скорее.Вместо того, чтобы возвращать полученные значения из этих вызовов next(), он делает Promise.resolve() ожидаемые значения для обещания и вызывает его метод then, передавая продолжение в качестве обратных вызовов.Вместо того, чтобы сигнализировать вызывающей стороне об окончании итерации при достижении return, он разрешает первоначально возвращенное обещание с возвращаемым значением.

2 голосов
/ 04 июля 2019

Не очень легко понять обещания и доходность, особенно если вы не знаете, как они работают под капотом. Итак, начнем с основ. Первое, что нужно понять, это то, что Javascript является однопоточным, что означает, что он может выполнять только одно действие одновременно. Вы по-прежнему можете использовать несколько вещей одновременно, потому что в JavaScript есть то, что называется циклом обработки событий.

Цикл событий в основном выглядит примерно так:

while(queue.waitForTasks()) {
   queue.performNextTask();
}

Цикл обработки событий проверяет наличие новых «задач» для запуска Javascript. Если есть задача. затем он выполняется до тех пор, пока не останется больше задач для выполнения. И он будет ждать своего нового задания. Эти задачи хранятся в чем-то, что называется очередью.

Обещания, Async / Await

Теперь мы понимаем, как Javascript обрабатывает различные задачи. Как это работает с обещаниями и async / await? promise - это не что иное, как задача, или, в случае Javascript, то, что содержит задачу, которая будет добавлена ​​в очередь и выполнена после выполнения всех задач перед ее выполнением. .then() - это способ обеспечения обратного вызова для вашего обещания, которое выполняется после вызова обратного вызова.

Ключевое слово await [something] сообщает Javascript: "Эй, поставь следующий [something] в конец своей очереди и вернись ко мне, как только у [something] будет результат.

Функция с ключевым словом async в основном говорит Javascript: «Эта функция - обещание, но выполняй ее немедленно».

Поток асинхронной функции проще всего понять / продемонстрировать с помощью двух разных асинхронных функций A и B, например:

const A = async () => {
    console.log(A: Start);
    for (var i = 0; i < 3; i++) {
        await (async () => console.log('A: ' + i));
    }
    console.log('A: Done');
}
const B = async () {
    console.log(B: Start);
    for (var i = 0; i < 3; i++) {
        await (async () => console.log('B: ' + i));
        await (async () => {/* A task without output */});
    }
    console.log('B: Done');
}

Когда вы вызываете ваши функции с await следующим образом:

console.log('Executing A');
await A();
console.log('Executing B');
await B();

это приведет к:

Executing A
A: Start
A: 0
A: 1
A: 2
A: Done
Executing B
B: Start
B: 0
B: 1
B: 2
B: Done

и работает:

console.log('Executing A');
A();
console.log('Executing B');
B();

приведет к:

Executing A
A: Start       Note: still gets ran before Executing B
Executing B
B: Start
A: 0
B: 0
A: 1
A: 2           Note: A: 2 first because another task in B was put in the queue
A: Done
B: 1
B: 2
B: Done

Понимание этого может помочь лучше понять поток вашего приложения.

выход

Ключевое слово yield похоже на await в том смысле, что «внешняя сила» контролирует, когда он продолжает выполнение функции. В этом случае не завершение задачи обещания, а функция generator.next()

0 голосов
/ 04 июля 2019

Я не знаю ответа на вопрос о правильной ментальной модели здесь, хотя мне бы очень хотелось узнать.

Но я нашел это интересным

Кайл Симпсон, автор книги «Ты, Дон»t Знать, что JS 'дал понять, как работает await на r / Javascript reddit - source :

"Это совершенно неверно. Генераторы не запускаются до завершения, и большинствоРеализации движка async-await фактически обрабатывают их как генераторы. Когда встречается выход, генератор локально приостанавливается ... буквально. Await использует тот же подход ".

"Нет, все это неправильная чепуха. Большинство движков обрабатывают async-await как генератор, который определенно делает локальную паузу с выходом. Оборачивание обещания .then () вокруг последующего кода будет одним из следующих:ждут самые наивные и неэффективные способы реализации. Даже если двигатель сделал это (большинство этого не делают), это не означает, что это правильная ментальная модель. Локальная пауза, как доходность, является правильной ментальной моделью. "

Но когда я лично смотрю на спецификацию скрипта ECMA и прохожу код с помощью отладчика vscode nodejs, await кажется гораздо более похожим на .then ()

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...