Почему функция синхронного сна не становится асинхронной из-за того, что находится внутри обещания?

Я пытаюсь понять обещания и то, как JavaScript работает с очередью, циклом событий и т. Д.

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

function syncSleep(ms){
    var end = new Date().getTime() + ms;
    var start = new Date().getTime();

    while (start < end) {
      start = new Date().getTime();
    }
}

function p() {
  return new Promise(function(resolve) {
     syncSleep(5000);
     resolve("syncSleep done!");
  });
}

p().then( function(s) {
  var div = document.getElementById('async');
  div.innerHTML = s;
} );

var div = document.getElementById('sync');
div.innerHTML = "This should appear right away! (but it doesn't)";

https://jsfiddle.net/7mw6m2x5/

Однако пользовательский интерфейс не отвечает, пока работает этот код.

Поэтому мне было интересно, может ли кто-нибудь объяснить, что здесь происходит? Являются ли обещания только способом обработки кода, который уже «сделан» асинхронным?

(Если да, то как это делается?)

Как мне справиться с медленным кодом синхронизации, если я не хочу, чтобы он зависал в пользовательском интерфейсе? Должен ли я использовать веб-воркер для этого?

Благодарен за любые разъяснения. Спасибо.


person john.abraham    schedule 07.05.2016    source источник
comment
и я мог бы использовать .then, чтобы справиться с этим, когда это было сделано Никакое значение не возвращается из syncSleep? Что мне делать с медленным кодом синхронизации, если я не хочу, чтобы он зависал в пользовательском интерфейсе? Что на самом деле реализуется javascript? Чего вы пытаетесь достичь?   -  person guest271314    schedule 07.05.2016
comment
@guest271314, я просто пытаюсь понять, как работает этот JavaScript. У меня была гипотеза о том, как этот код будет работать, но оказалось, что я ошибался. Так что теперь я пытаюсь исправить свое понимание :-)   -  person john.abraham    schedule 07.05.2016
comment
Да, вы должны использовать для этого WebWorkers или child_process/cluster. Невозможно обрабатывать код медленной синхронизации в однопоточном языке. В качестве альтернативы, если вам разрешено изменять медленный код, вы можете переписать его, используя генераторы/итераторы ES6 или как они там называются.   -  person Florian Wendelborn    schedule 07.05.2016
comment
@john.abraham Единственное, что удалось отобразить "This should appear right away! (but it doesn't)" до завершения while, — это использовать alert(), то есть вызвать собственную глобальную функцию. В качестве альтернативы можно просто обновить DOM перед вызовом цикла while в syncSleep   -  person guest271314    schedule 07.05.2016
comment
Совет API: не используйте new Date().getTime(), используйте Date.now(). Тот же результат, не тратится память на выделение объектов и не тратится время на ненужный сборщик мусора.   -  person Mike 'Pomax' Kamermans    schedule 07.05.2016
comment
@john.abraham jsfiddle.net/7mw6m2x5/2 , jsfiddle.net/7mw6m2x5/3   -  person guest271314    schedule 07.05.2016
comment
Промисы не делают синхронную операцию волшебным образом асинхронной. Это всего лишь инструмент, который поможет вам управлять обратными вызовами и состоянием завершения/ошибки.   -  person jfriend00    schedule 07.05.2016


Ответы (3)


Код работает так, как ожидалось.

Поскольку Javascript является однопоточным, пользовательский интерфейс будет блокироваться во время выполнения вашего цикла.

Обещания — это просто хороший способ обработки асинхронного кода. Смотрите введение здесь:

http://www.html5rocks.com/en/tutorials/es6/promises/

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

Чтобы иметь возможность поддерживать отзывчивость пользовательского интерфейса, пока другой код выполняется в фоновом режиме, вам необходимо использовать WebWorkers:

https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers

Цитата со страницы, указанной выше:

«Веб-воркеры предоставляют веб-контенту простые средства для запуска скриптов в фоновых потоках. Рабочий поток может выполнять задачи, не мешая пользовательскому интерфейсу».

Обновление:

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

function setVal(s) {
  var divAsync = document.getElementById('async');
  var innerDiv = document.createElement('div');
  innerDiv.innerHTML = s + '<br>';
  divAsync.appendChild(innerDiv);
}


function syncSleep(ms) {
  var end = new Date().getTime() + ms;
  var now = new Date().getTime();
  var stepBegin = new Date().getTime();
  var step = 0;

  // This loop is blocking
  // The UI will only refresh after the loop completion
  while (now < end) {
    now = new Date().getTime();
    step = now - stepBegin;
    if (step >= 1000) {
      setVal(now);
      step = 0;
      stepBegin = now;
    }
  }

}

function pBlock() {
  return new Promise(function(resolve) {
    syncSleep(3200);
    resolve("pBlock syncSleep done!");
  });
}


function noBlockUpdate(ms, resolve) {
  var end = new Date().getTime() + ms;
  var now = new Date().getTime();
  var stepBegin = new Date().getTime();
  var step = 0;

  function noBlock() {
    now = new Date().getTime();

    // paint every 1000ms;
    step = now - stepBegin;
    if (step >= 1000) {
      setVal(now);
      step = 0;
      stepBegin = now;
    }

    if (now < end) {
      // NB: this is going to be called thousands of times
      // But the UI will still update every 1000 ms
      setTimeout(noBlock);
    } else {
      resolve("pNoBlock done!");
    }
  };
  noBlock();
}

function pNoBlock() {
  return new Promise(function(resolve) {
    noBlockUpdate(3200, resolve);
    setVal("pNoBlock launched!");
  });
}



pNoBlock().then(setVal);

var divSync = document.getElementById('sync');
divSync.innerHTML = "This appears right away!";



// Just wait 4 seconds so the non-blocking code completes
setTimeout(function() {
  // Clear all div's
  document.getElementById('sync').innerHTML = '';
  document.getElementById('async').innerHTML = '';

  var divSync = document.getElementById('sync');
  divSync.innerHTML = "This does not appear right away, only after the blocking operation is complete!";

  pBlock().then(setVal);
}, 4000);
<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>

<body>

  <div id="sync"></div>

  <div id="async"></div>

</body>

</html>

person VRPF    schedule 07.05.2016
comment
Но почему код внутри Promise не помещается в конец очереди? т.е. почему код зависает сразу а не после div.innerHTML=...(внизу) - person john.abraham; 07.05.2016
comment
Page JS имеет только один доступный поток (если вам нужна работа вне потока, вам нужно использовать веб-работников или более общий сервисный работник). Ваше обещание может и, таким образом, немедленно выполняется, и поэтому немедленно блокирует поток, запуская ваш (невероятно плохой, потому что это заставит ЦП работать с перегрузкой - пять секунд 100% бездействия ЦП...) спин-цикл. - person Mike 'Pomax' Kamermans; 07.05.2016
comment
@Mike'Pomax'Kamermans Если я использую setTimeout(()=›{syncSleep(5000);resolve(syncSleep done!!)},0) внутри промиса, он ведет себя так, как я ожидал. Правильно ли я думаю, что это связано с тем, что setTimeout() предоставляется DOM и на самом деле запускает syncSleep в другом потоке? - person john.abraham; 07.05.2016
comment
не совсем. setTimeout планирует, что все, что вы передаете, должно быть оценено как минимум через X мс, когда поток простаивает. Здесь важна по крайней мере часть: если какой-то путь кода блокирует поток дольше, чем требуется для вашего тайм-аута, ваш тайм-аут сработает после того, как этот блок разрешится. Итак, поскольку ваш код на самом деле больше ничего не делает, тайм-аут работает правильно, но если бы у вас была целая куча плохо написанного синхронного кода, который выполнялся после установки вашего промиса, поток был бы занят до тех пор, пока не закончит его обработку. Страница JS на самом деле имеет только один поток. - person Mike 'Pomax' Kamermans; 07.05.2016
comment
@Mike'Pomax'Kamermans На самом деле, когда я это делаю, иногда работает так, как я изначально ожидал, но иногда он зависает. В чем дело? - person john.abraham; 07.05.2016
comment
@john.abraham Я попытался ответить на ваши вопросы с обновлением - person VRPF; 08.05.2016

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

Да. Обещания на самом деле не создают асинхронных операций. Их намерение состоит в том, чтобы упростить работу с асинхронными операциями, определив согласованный API. Но они не избегают блокировки сами по себе, кроме одной небольшой задержки между вызовами обратных вызовов resolve() и .then().

Функция, предоставляемая конструктору Promise, вызывается немедленно и синхронно. И как только обратный вызов .then() начнет выполняться, он должен будет выполниться до завершения, прежде чем движок сделает что-нибудь еще.

Что делать с медленным кодом синхронизации, если я не хочу, чтобы он зависал в пользовательском интерфейсе?

Старайтесь избегать длительных синхронных операций, насколько это возможно. Асинхронной альтернативой syncSleep() будет setTimeout().

function p() {
  return new Promise(function(resolve) {
    setTimeout(function () {
      resolve("syncSleep done!");
    }, 5000);
  });
}

Если нельзя избежать длительной синхронной операции, то вам следует попытаться переместить ее в отдельный процесс/экземпляр движка, а в браузерах это можно сделать с помощью Web Workers.

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

person Jonathan Lonowski    schedule 07.05.2016
comment
Спасибо за ваш ответ, конечно, я бы никогда не использовал функцию синхронного сна в реальном приложении. Я сделал это по педагогическим соображениям; пытаясь понять, как это работает. - person john.abraham; 07.05.2016

Обещания — это не темы. Это просто сахар для обработки событий успеха и неудачи (обратных вызовов) в однопоточном коде.

Обратный вызов конструктора new Promise(cb) выполняется немедленно и синхронно. Обратные вызовы, переданные .then(cb)/.catch(cb), выполняются на следующем тике после того, как обещание разрешено/отклонено, но они также выполняются в том же — и только — потоке.

person Kornel    schedule 07.05.2016