InnerHTML нельзя доверять: не всегда выполняется синхронно - программирование
Подтвердить что ты не робот

InnerHTML нельзя доверять: не всегда выполняется синхронно

Чтобы увидеть проблему в действии, см. этот jsbin. Нажатие на кнопку вызывает buttonHandler(), который выглядит следующим образом:

function buttonHandler() {
  var elm = document.getElementById("progress");
  elm.innerHTML = "thinking";
  longPrimeCalc();
}

Вы ожидаете, что этот код изменит текст div на "мышление", а затем запустит longPrimeCalc(), арифметическую функцию, которая занимает несколько секунд. Однако это не то, что происходит. Вместо этого "longPrimeCalc" завершается первым, а затем текст обновляется до "мышления" после его выполнения, как если бы порядок двух строк кода был отменен.

Похоже, что браузер не запускает код "innerHTML" синхронно, но вместо этого создает для него новый поток, который выполняется на собственном отдыхе.

Мои вопросы:

  • Что происходит под капотом, что ведет к такому поведению?
  • Как я могу заставить браузер вести себя так, как я ожидал бы, т.е. заставить его обновлять "innerHTML" до того, как он выполнит "longPrimeCalc()"?

Я тестировал это в последней версии хром.

4b9b3361

Ответ 1

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

  • Чтобы исправить это, вы должны завершить текущий код, а затем начать вычисление в другом контексте. Вы можете сделать это с помощью setTimeout. Я не уверен, есть ли другой способ помимо этого.

Вот jsfiddle, показывающий поведение. Вам не нужно передавать обратный вызов на longPrimeCalc, вам просто нужно создать другую функцию, которая делает то, что вы хотите, с возвращаемым значением. По сути, вы хотите отложить вычисление до другого "потока" выполнения. Написание кода таким образом делает очевидным, что вы делаете (обновлено снова, чтобы сделать его потенциально более приятным):

function defer(f, callback) {
  var proc = function() {
    result = f();
    if (callback) {
      callback(result);
    }
  }
  setTimeout(proc, 50);
}

function buttonHandler() {
  var elm = document.getElementById("progress");
  elm.innerHTML = "thinking...";
  defer(longPrimeCalc, function (isPrime) {
    if (isPrime) {
      elm.innerHTML = "It was a prime!";
    }
    else {
      elm.innerHTML = "It was not a prime =(";
    }
  });
}

Ответ 2

Ваша догадка неверна. Обновление .innerHTML завершается синхронно (и браузер определенно не создает новый поток). Браузер просто не хочет обновлять окно, пока ваш код не будет закончен. Если вы должны были допросить DOM каким-то образом, чтобы обновить представление, браузер не будет иметь выбора.

Например, сразу после установки innerHTML добавьте эту строку:

var sz = elm.clientHeight; // whoops that not it; hold on ...

edit — Я мог бы найти способ обмануть браузер, или это может быть невозможно; конечно, верно, что запуск длинных вычислений в отдельном цикле событий заставит его работать:

setTimeout(longPrimeCalc, 10); // not 0, at least not with Firefox!

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