Блокируют ли обещания Javascript стек

#javascript #promise #async-await #dom-events #event-loop

#javascript #обещание #async-await #dom-события #цикл событий

Вопрос:

При использовании обещаний Javascript блокируется ли цикл событий?

Насколько я понимаю, использование await amp; async останавливает работу стека до завершения операции. Делает ли это, блокируя стек, или он действует аналогично обратному вызову и передаче процесса в какой-либо API?

Комментарии:

1. Насколько я понимаю, в этом весь смысл обещания.

2. «действует ли это подобно обратному вызову» — в общем смысле обратный вызов может вызываться асинхронно или синхронно в зависимости от контекста. Но весь смысл обещаний заключается в том, чтобы разрешить асинхронные (неблокирующие) операции.

3. Нет «стека» в том смысле, в котором вы, похоже, его используете. Я думаю, вы имеете в виду «цикл событий». Возможно, вам следует отредактировать свой заголовок, а также текст вашего вопроса.

Ответ №1:

При использовании обещаний Javascript блокируется ли цикл событий?

Нет. Обещания — это только система уведомлений о событиях. Сами по себе они не являются операцией. Они просто реагируют на разрешение или отклонение, вызывая соответствующие .then() .catch() обработчики or, и, если они связаны с другими обещаниями, они могут отложить вызов этих обработчиков до тех пор, пока обещания, к которым они привязаны, также не будут разрешены / отклонены. Таким образом, одно обещание ничего не блокирует и, конечно же, не блокирует цикл событий.

Насколько я понимаю, использование await amp; async останавливает работу стека до завершения операции. Делает ли это, блокируя стек, или он действует аналогично обратному вызову и передаче процесса в какой-либо API?

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


Обратите внимание на один из комментариев ниже:

Теперь, если бы вы создали код, который перегружает цикл событий непрерывным разрешением обещаний (в каком-то бесконечном цикле, как предлагается в некоторых комментариях здесь), тогда цикл событий будет снова и снова обрабатывать эти постоянно разрешаемые обещания из очереди микрозадач и никогда не получит возможности обрабатывать ожидающие макрозадачив цикле событий (другие типы событий). Цикл событий все еще выполняется и все еще обрабатывает микрозадачи, но если вы постоянно добавляете в него новые микрозадачи (разрешенные обещания), он может никогда не перейти к макрозадачам. Кажется, есть некоторые споры о том, можно ли назвать это «блокировкой цикла событий» или нет. Это всего лишь вопрос терминологии — важнее то, что происходит на самом деле. В этом примере бесконечного цикла, постоянно разрешающего новое обещание снова и снова, цикл событий продолжит обработку этих разрешенных обещаний, а другие события в очереди событий не будут обработаны, потому что они никогда не попадают в начало очереди, чтобы получить свою очередь. Это чаще называют «голоданием», чем «блокировкой», но дело в том, что макрозадачи могут не обслуживаться, если вы постоянно и бесконечно помещаете новые микрозадачи в очередь.

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

Комментарии:

1. Я считаю, что это зависит от того, блокируется ли логика, выполняемая в обещании. Логика с интенсивным использованием процессора заблокирует цикл событий независимо от каких-либо обещаний переноса.

2. «Поскольку такое обещание ничего не блокирует и, конечно же, не блокирует цикл событий.», затем попробуйте выполнить const p = () => Promise.resolve().then(p); p() .

3. @Kaiido — мне кажется, что это создаст бесконечный цикл? Какую возможную проблему вы решаете с этим? И зачем вам это делать? Если вы просто пытаетесь противостоять моей логике, пожалуйста, противопоставьте ей реальный полезный код, а не намеренно вредоносный код.

4. Да, это создает бесконечный цикл, который определенно блокирует цикл событий. Ваш первый абзац отключен.

5. @Kaiido — Итак, вы утверждаете, что вредоносный код может быть написан с использованием обещаний, которые испортят цикл событий. Да, вы можете это сделать, если действительно хотите. Я не уверен, какое значение вы только что добавили в это обсуждение. И мое предложение остается в силе, как написано. Ни одно обещание не блокирует цикл событий. Ваш бесконечный цикл блокирует цикл событий, многократно запуская одну и ту же функцию снова и снова.

Ответ №2:

Блокируют ли обещания Javascript стек

Нет, не стек. Текущее задание будет выполняться до завершения, прежде чем начнется выполнение обратного вызова Promise.

При использовании обещаний Javascript блокируется ли цикл событий?

Да, это так.

Разные среды имеют разные модели обработки цикла событий, поэтому я буду говорить об одной в браузерах, но, хотя модель nodejs немного проще, они фактически демонстрируют одинаковое поведение.

В браузере обратные вызовы Promises (PromiseReactionJob в терминах ES) фактически выполняются в так называемой микрозадаче.
Микрозадача — это специальная задача, которая помещается в очередь в специальную очередь микрозадач. Эта очередь микрозадач посещается несколько раз в течение одной итерации цикла событий в так называемой контрольной точке микрозадачи, и каждый раз, когда стек вызовов JS пуст, например, после выполнения основной задачи, после выполнения событий рендеринга, таких как изменение размера, После каждого обратного вызова анимационного кадра и т. Д.

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

Это означает, что простой факт использования обещания не заставляет ваш код пропускать цикл событий, как setTimeout() это могло бы сделать запланированное задание, и даже несмотря на то, что стек js был опустошен, а предыдущая задача была полностью выполнена до вызова обратного вызова, вы все равно можете полностью заблокировать цикл событий.цикл событий, никогда не позволяющий ему обрабатывать какие-либо другие задачи или даже обновлять рендеринг:

 const log = document.getElementById( "log" );
let now = performance.now();
let i = 0;

const promLoop = () => {
  // only the final result will get painted
  // because the event-loop can never reach the "update the rendering steps"
  log.textContent = i  ;
  if( performance.now() - now < 5000 ) {
    // this doesn't let the event-loop loop
    return Promise.resolve().then( promLoop );
  }
  else { i = 0; }
};

const taskLoop = () => {
  log.textContent = i  ;
  if( performance.now() - now < 5000 ) {
    // this does let the event-loop loop
    postTask( taskLoop );
  }
  else { i = 0; }
};

document.getElementById( "prom-btn" ).onclick = start( promLoop );
document.getElementById( "task-btn" ).onclick = start( taskLoop );

function start( fn ) {
  return (evt) => {
    i = 0;
    now = performance.now();
    fn();
  };
}

// Posts a "macro-task".
// We could use setTimeout, but this method gets throttled
// to 4ms after 5 recursive calls.
// So instead we use either the incoming postTask API
// or the MesageChannel API which are not affected
// by this limitation
function postTask( task ) {
  // Available in Chrome 86  under the 'Experimental Web Platforms' flag
  if( window.scheduler ) {
    return scheduler.postTask( task, { priority: "user-blocking" } );
  }
  else {
    const channel = postTask.channel ||= new MessageChannel();
    channel.port1
      .addEventListener( "message", () => task(), { once: true } );
    channel.port2.postMessage( "" );
    channel.port1.start();
  }
}  
 <button id="prom-btn">use promises</button>
<button id="task-btn">use postTask</button>
<pre id="log"></pre>  

Так что будьте осторожны, использование Promise совсем не помогает разрешить цикл событий на самом деле.

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

Насколько я понимаю, использование await amp; async останавливает работу стека до завершения операции.

Своего рода … при await вводе значения он добавит оставшуюся часть выполнения функции к обратным вызовам, присоединенным к ожидаемому обещанию (которое может быть новым обещанием, разрешающим значение, не являющееся обещанием).
Таким образом, стек действительно очищается в это время, но цикл событий здесь вообще не блокируется, наоборот, он был освобожден для выполнения чего-либо еще, пока обещание не будет выполнено.

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

 async function fn() {

  console.log( "will wait a bit" );
  const prom = await new Promise( (res, rej) => {} );
  console.log( "done waiting" );
  
}
fn();

onmousemove = () => console.log( "still alive" );  
 move your mouse to check if the page is locked  

Комментарии:

1. Это самый образованный ответ

Ответ №3:

await Блокируется только текущий async function , цикл событий продолжает выполняться нормально. Когда обещание выполняется, выполнение тела функции возобновляется с того места, где оно остановилось.

Каждый async / await может быть преобразован в эквивалентную .then(…) программу обратного вызова и работает точно так же с точки зрения параллелизма. Таким образом, во время редактирования обещания await могут возникать другие события и может выполняться произвольный другой код.

Комментарии:

1. «Ожидание блокирует только текущую асинхронную функцию» — я думаю, что это нуждается в лучшей формулировке, «блоки» здесь сбивают с толку.

2. @zerkms Есть какие-нибудь предложения? На самом деле я думаю, что » блокировка » вполне уместна, если вы рассматриваете каждый вызов для async function создания задачи / процесса.

3. Возможно. Всю async/await концепцию не так просто объяснить, и делать это правильными словами выше моих языковых способностей.

Ответ №4:

Как и другие упомянутые выше… Обещания похожи на систему уведомлений о событиях и async/await такие же, как then() . Однако будьте очень осторожны, вы можете «заблокировать» цикл событий, выполнив операцию блокировки. Взгляните на следующий код:

 function blocking_operation_inside_promise(){
    return new Promise ( (res, rej) => {
        while( true ) console.log(' loop inside promise ')
        res();
    })
}

async function init(){
    let await_forever = await blocking_operation_inside_promise()
}

init()
console.log('END')
  

КОНЕЧНЫЙ журнал никогда не будет напечатан. JS является однопоточным, и этот поток сейчас занят. Вы могли бы сказать, что все это «заблокировано» операцией блокировки. В этом конкретном случае цикл событий сам по себе не блокируется, но он не будет доставлять события в ваше приложение, потому что основной поток занят.

JS / Node может быть очень полезным языком программирования, очень эффективным при использовании неблокирующих операций (например, сетевых операций). Но не используйте его для выполнения очень интенсивных алгоритмов процессора. Если вы находитесь в браузере, рассмотрите возможность использования веб-рабочих, если вы находитесь на стороне сервера, используйте рабочие потоки, дочерние процессы или микросервисную архитектуру.