openlang

В ECMA-262 нет ни слова о цикле событий

В ECMA-262 цикл событий не упоминается ни разу. Спецификация определяет задания (jobs) и хост-хук HostEnqueuePromiseJob, который движок вызывает, когда реакция промиса готова к запуску. Когда эти задания на самом деле выполняются и что идёт между ними, решает хост.

Поэтому setTimeout в спецификации языка вообще нет, и спор о том, кто раньше, колбэк таймера или колбэк .then, одним чтением ECMA-262 не решить. Порядок, на который все полагаются, когда все задания промисов выполняются до следующей задачи таймера, задают цикл событий HTML и Node, а не базовая спецификация.

Разделение всё равно полезное. В пределах одного хода реакции промисов выполняются в порядке постановки в очередь, а await даже на уже разрешённом значении ждёт задание, так что код после него синхронно не выполнится никогда. В Dart та же двухуровневая схема, и работа из scheduleMicrotask идёт раньше ожидающих событий.

Хосты договорились между собой о большем, чем обязаны. Жаль только, что объяснения так часто начинаются со схемы, которую спецификация никогда не рисовала.

Разговор продолжается 2

Следуя доводам
  1. Ссылка на ответ

    Это почти один в один ложится на дельта-циклы. В VHDL присваивание сигналу никогда не действует немедленно. Оно планирует новое значение, симулятор доводит до конца все проснувшиеся процессы, затем обновляет сигналы и будит процессы, чувствительные к ним, и всё это без продвижения модельного времени. Время сдвигается, только когда не осталось ожидающих дельта-циклов.

    Если читать «обновление сигнала» как «задание промиса», а «шаг времени» как «задачу», структура совпадает. Цепочка .then, которая всё время ставит новую работу в очередь, морит таймеры голодом так же, как комбинационная петля держит симулятор в одной точке времени. GHDL останавливает такой прогон, когда упирается в лимит дельта-циклов. Вкладка браузера просто зависает.

    Одно различие стоит помнить. Процессы, возобновлённые в одном дельта-цикле, читают одни и те же значения сигналов при любом порядке выполнения. Задания промисов видят побочные эффекты друг друга сразу.

  2. Ссылка на ответ

    В SystemVerilog аналогия ещё ближе, потому что каждый временной слот разбит на именованные области. Active, Inactive, NBA, затем Observed и Reactive для утверждений и program-блоков, и ещё несколько. Блокирующее присваивание выполняется в Active, #0 переводит процесс в Inactive, а обновление от неблокирующего <= ждёт области NBA.

    Тестбенч, утыканный #0, чтобы обойти гонку, делает то же самое, что await null, пропускающий вперёд ожидающий .then. Работает, пока кто-нибудь не добавит ещё один. Лучше читать через clocking-блок, тогда выборка происходит в определённой точке относительно тактового сигнала.