Почему setTimeout(fn, 0) иногда полезен?

Недавно я столкнулся с довольно неприятной ошибкой, когда код загружал <select> динамически через JavaScript. Это динамически загружается <select> имел предварительно выбранное значение. В IE6 у нас уже был код для исправления выбранного <option>потому что иногда <select>"s selectedIndex значение будет не синхронизировано с выбранным <option>"s index атрибут, как показано ниже:

field.selectedIndex = element.index;

Однако этот код не работал. Хотя поле selectedIndex был установлен правильно, неправильный индекс в конечном итоге будет выбран. Однако, если я застрял alert() в нужное время, будет выбран правильный вариант. Думая, что это может быть какая-то проблема с синхронизацией, я попробовал что-то случайное, что раньше видел в коде

var wrapFn = (function() {
    var myField = field;
    var myElement = element;

    return function() {
        myField.selectedIndex = myElement.index;
    }
})();
setTimeout(wrapFn, 0);

И это сработало!

У меня есть решение для моей проблемы, но мне неприятно, что я не знаю точно, почему это решает мою проблему. У кого-нибудь есть официальное объяснение? Какую проблему с браузером я избегаю, вызывая свою функцию "позже", используя setTimeout()?

19 ответов

Решение

Это работает, потому что вы делаете совместную многозадачность.

Браузер должен делать сразу несколько вещей, и только один из них - выполнять JavaScript. Но одна из вещей, для которой JavaScript очень часто используется - это попросить браузер создать элемент отображения. Часто предполагается, что это делается синхронно (в частности, поскольку JavaScript не выполняется параллельно), но нет никакой гарантии, что это так, и JavaScript не имеет четко определенного механизма ожидания.

Решение состоит в том, чтобы "приостановить" выполнение JavaScript, чтобы потоки рендеринга наверстали упущенное. И это эффект, который setTimeout() с таймаутом 0 делает. Это похоже на выход потока / процесса в C. Хотя кажется, что он говорит: "Запустите это немедленно", на самом деле он дает браузеру возможность завершить некоторые вещи, не связанные с JavaScript, которые ожидали завершения, прежде чем приступить к этому новому фрагменту JavaScript.,

(В действительности, setTimeout() повторно помещает новый JavaScript в конец очереди выполнения. Смотрите комментарии для ссылок на более подробное объяснение.)

IE6 просто более склонен к этой ошибке, но я видел, что это происходит в более старых версиях Mozilla и в Firefox.


Смотрите выступление Филиппа Робертса "Какого черта цикл событий?" для более подробного объяснения.

Предисловие:

ВАЖНОЕ ПРИМЕЧАНИЕ: Несмотря на то, что большинство проголосовало и принято, принятый ответ @staticsan на самом деле НЕ ПРАВИЛЬНО! - см. комментарий Дэвида Малдера для объяснения причин.

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

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

ОБНОВЛЕНИЕ: Я сделал JSFiddle, чтобы продемонстрировать объяснение ниже: http://jsfiddle.net/C2YBE/31/. Большое спасибо @ThangChung за помощь в его запуске.

ОБНОВЛЕНИЕ 2: На случай, если веб-сайт JSFiddle умрет или удалит код, я добавил код к этому ответу в самом конце.


ДЕТАЛИ:

Представьте себе веб-приложение с кнопкой "сделать что-то" и div результата.

onClick Обработчик кнопки "сделать что-то" вызывает функцию "LongCalc()", которая делает 2 вещи:

  1. Делает очень долгий расчет (скажем, занимает 3 минуты)

  2. Печатает результаты расчета в результате div.

Теперь ваши пользователи начинают тестировать это, нажимают кнопку "сделать что-то", и страница сидит там, ничего не делая в течение 3 минут, они становятся беспокойными, нажимают кнопку снова, ждут 1 минуту, ничего не происходит, снова нажимают кнопку…

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


Таким образом, вы добавляете DIV "Status" (изначально пустой) и изменяете onclick обработчик (функция LongCalc()) сделать 4 вещи:

  1. Заполните статус "Расчет... может занять ~3 минуты" в статус DIV

  2. Делает очень долгий расчет (скажем, занимает 3 минуты)

  3. Печатает результаты расчета в результате div.

  4. Заполните статус "Расчет выполнен" в статус DIV

И вы с радостью отдаете приложение пользователям для повторного тестирования.

Они возвращаются к тебе, выглядя очень злыми. И объясните, что когда они нажимали кнопку, статус DIV никогда не обновлялся со статусом "Расчет..."!!!


Вы почесываете голову, спрашиваете о Stackru (или читаете документы, или Google), и понимаете, что проблема:

Браузер помещает все свои задачи "TODO" (как задачи пользовательского интерфейса, так и команды JavaScript), возникающие в результате событий, в одну очередь. И, к сожалению, перерисовка DIV "Status" с новым значением "Calculation..." - это отдельный TODO, который идет в конец очереди!

Вот разбивка событий во время теста вашего пользователя, содержимое очереди после каждого события:

  • Очередь: [Empty]
  • Событие: нажмите на кнопку. Очередь за событием: [Execute OnClick handler(lines 1-4)]
  • Событие: выполнить первую строку в обработчике OnClick (например, изменить значение Status DIV). Очередь за событием: [Execute OnClick handler(lines 2-4), re-draw Status DIV with new "Calculating" value], Обратите внимание, что, хотя изменения DOM происходят мгновенно, для перерисовки соответствующего элемента DOM вам необходимо новое событие, вызванное изменением DOM, которое произошло в конце очереди.
  • ПРОБЛЕМА!!! ПРОБЛЕМА!!! Подробности объяснены ниже.
  • Событие: выполнить вторую строку в обработчике (расчет). Очередь после: [Execute OnClick handler(lines 3-4), re-draw Status DIV with "Calculating" value],
  • Событие: выполнить 3-ю строку в обработчике (заполнить результат DIV). Очередь после: [Execute OnClick handler(line 4), re-draw Status DIV with "Calculating" value, re-draw result DIV with result],
  • Событие: выполнить 4-ю строку в обработчике (заполнить статус DIV "DONE"). Очередь: [Execute OnClick handler, re-draw Status DIV with "Calculating" value, re-draw result DIV with result; re-draw Status DIV with "DONE" value],
  • Событие: выполнить подразумевается return от onclick обработчик саб. Мы убираем обработчик "Execute OnClick" из очереди и начинаем выполнение следующего элемента в очереди.
  • ПРИМЕЧАНИЕ: поскольку мы уже завершили расчет, для пользователя уже прошло 3 минуты. Событие повторного розыгрыша еще не произошло!!!
  • Событие: перерисовать статус DIV со значением "Расчет". Мы делаем перерисовку и снимаем это с очереди.
  • Событие: перерисовать Result DIV со значением результата. Мы делаем перерисовку и снимаем это с очереди.
  • Событие: перерисовать статус DIV со значением "Готово". Мы делаем перерисовку и снимаем это с очереди. Зрители с острыми глазами могут даже заметить, что "Status DIV" со значением "Calculation" мигает в течение доли микросекунды - ПОСЛЕ ЗАКОНЧЕННОГО РАСЧЕТА

Таким образом, основная проблема заключается в том, что событие перерисовки для DIV "Статус" помещается в очередь в конце, ПОСЛЕ события "Выполнение строки 2", которое занимает 3 минуты, поэтому реальное перерисовывание не происходит, пока ПОСЛЕ расчета сделано.


На помощь приходит setTimeout(), Как это помогает? Потому что, вызывая долго выполняющийся код через setTimeout вы фактически создаете 2 события: setTimeout само выполнение и (из-за 0 тайм-аута) отдельная запись в очереди для исполняемого кода.

Итак, чтобы решить вашу проблему, вы измените onClick обработчик для двух операторов (в новой функции или просто в блоке onClick):

  1. Заполните статус "Расчет... может занять ~3 минуты" в статус DIV

  2. казнить setTimeout() с 0 тайм-аутом и вызовом LongCalc() функция

    LongCalc() функция почти такая же, как и в прошлый раз, но, очевидно, не имеет статуса DIV "Расчет...", обновление первого шага; и вместо этого сразу начинает расчет.

Итак, как теперь выглядит последовательность событий и очередь?

  • Очередь: [Empty]
  • Событие: нажмите на кнопку. Очередь за событием: [Execute OnClick handler(status update, setTimeout() call)]
  • Событие: выполнить первую строку в обработчике OnClick (например, изменить значение Status DIV). Очередь за событием: [Execute OnClick handler(which is a setTimeout call), re-draw Status DIV with new "Calculating" value],
  • Событие: выполнить вторую строку в обработчике (вызов setTimeout). Очередь после: [re-draw Status DIV with "Calculating" value], В очереди нет ничего нового еще 0 секунд.
  • Событие: Тревога по тайм-ауту отключается через 0 секунд. Очередь после: [re-draw Status DIV with "Calculating" value, execute LongCalc (lines 1-3)],
  • Событие: перерисовать статус DIV со значением "Расчет". Очередь после: [execute LongCalc (lines 1-3)], Обратите внимание, что это событие перерисовки может произойти, прежде чем сработает сигнализация, которая работает так же хорошо.
  • ...

Ура! Статус DIV только что обновился до "Расчет...", прежде чем начался расчет!!!



Ниже приведен пример кода из JSFiddle, иллюстрирующий эти примеры: http://jsfiddle.net/C2YBE/31/:

HTML код:

<table border=1>
    <tr><td><button id='do'>Do long calc - bad status!</button></td>
        <td><div id='status'>Not Calculating yet.</div></td>
    </tr>
    <tr><td><button id='do_ok'>Do long calc - good status!</button></td>
        <td><div id='status_ok'>Not Calculating yet.</div></td>
    </tr>
</table>

Код JavaScript: (Выполнено на onDomReady и может потребовать JQuery 1.9)

function long_running(status_div) {

    var result = 0;
    // Use 1000/700/300 limits in Chrome, 
    //    300/100/100 in IE8, 
    //    1000/500/200 in FireFox
    // I have no idea why identical runtimes fail on diff browsers.
    for (var i = 0; i < 1000; i++) {
        for (var j = 0; j < 700; j++) {
            for (var k = 0; k < 300; k++) {
                result = result + i + j + k;
            }
        }
    }
    $(status_div).text('calculation done');
}

// Assign events to buttons
$('#do').on('click', function () {
    $('#status').text('calculating....');
    long_running('#status');
});

$('#do_ok').on('click', function () {
    $('#status_ok').text('calculating....');
    // This works on IE8. Works in Chrome
    // Does NOT work in FireFox 25 with timeout =0 or =1
    // DOES work in FF if you change timeout from 0 to 500
    window.setTimeout(function (){ long_running('#status_ok') }, 0);
});

Взгляните на статью Джона Резига о том, как работают таймеры JavaScript. Когда вы устанавливаете тайм-аут, он фактически ставит в очередь асинхронный код, пока механизм не выполнит текущий стек вызовов.

setTimeout() покупает вас некоторое время, пока элементы DOM не будут загружены, даже если установлено в 0.

Проверьте это: setTimeout

В большинстве браузеров есть процесс, называемый основным потоком, который отвечает за выполнение некоторых задач JavaScript, обновлений пользовательского интерфейса, например: рисование, перерисовка или перекомпоновка и т. Д.

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

Когда обновления пользовательского интерфейса генерируются, когда основной поток занят, задачи добавляются в очередь сообщений.

setTimeout(fn, 0); Добавь это fn до конца очереди, которая будет выполнена. Он планирует задачу, которая будет добавлена ​​в очередь сообщений через определенный промежуток времени.

Здесь есть противоречивые ответы с отрицательным голосом, и без доказательств невозможно узнать, кому верить. Вот доказательство того, что @DVK является правильным, а @SalvadorDali - неправильным. Последний утверждает:

"И вот почему: невозможно задать setTimeout с задержкой 0 миллисекунд. Минимальное значение определяется браузером, и оно не равно 0 миллисекундам. Исторически браузеры устанавливают этот минимум на 10 миллисекунд, но спецификации HTML5 и современные браузеры имеют значение 4 миллисекунды."

Минимальное время ожидания 4 мсек не имеет отношения к происходящему. Что действительно происходит, так это то, что setTimeout помещает функцию обратного вызова в конец очереди выполнения. Если после setTimeout(callback, 0) у вас есть код блокировки, выполнение которого занимает несколько секунд, обратный вызов не будет выполняться в течение нескольких секунд, пока код блокировки не завершится. Попробуйте этот код:

function testSettimeout0 () {
    var startTime = new Date().getTime()
    console.log('setting timeout 0 callback at ' +sinceStart())
    setTimeout(function(){
        console.log('in timeout callback at ' +sinceStart())
    }, 0)
    console.log('starting blocking loop at ' +sinceStart())
    while (sinceStart() < 3000) {
        continue
    }
    console.log('blocking loop ended at ' +sinceStart())
    return // functions below
    function sinceStart () {
        return new Date().getTime() - startTime
    } // sinceStart
} // testSettimeout0

Выход:

setting timeout 0 callback at 0
starting blocking loop at 5
blocking loop ended at 3000
in timeout callback at 3033

Если вы не хотите смотреть видео целиком , вот простое объяснение того, что нужно понимать, чтобы понять ответ на этот вопрос:

  1. JavaScript является однопоточным , то есть во время работы он выполняет только одну задачу.
  2. Но среды, в которых работает JavaScript, могут быть многопоточными. Например, браузеры чаще всего являются многопоточными созданиями, т. Е. Могут выполнять несколько задач одновременно. Таким образом, они могут запускать JavaScript и в то же время отслеживать работу с другими вещами.

С этого момента мы будем говорить о «JavaScript в браузерах», поскольку подобные вещи действительно предоставляются браузерами , а не являются частью самого JavaScript.

  1. То, что позволяет JavaScript работать асинхронно, - это действительно многопоточный браузер!Он предоставляет JavaScript другое «пространство» для размещения вещей (подробнее об этом позже), кроме основного пространства, которое он использует (называемого « стек вызовов », куда JavaScript помещает каждую строку кода, когда он хочет запустить их).

Теперь давайте назовем это другое пространство « вторым пространством » (сейчас оно не на 100% точно, но очень похоже на суть реальности).

  1. Скажем fnэто функция. Важно понимать, что вызов не равен setTimeout(fn, 0);звоните . Вот что я объясню ниже.

Во-первых, предположим еще одну задержку, например 5000 миллисекунд:. Важно отметить, что это все еще «вызов функции», верно? Так что его нужно поместить в основное пространство и удалить из него, когда это будет сделано, но «подождите!», Нам не нравится целая длительная и скучная 5-секундная задержка, верно ?! Это заблокировало бы основное пространство и не позволило бы JavaScript запускать НИЧЕГО еще.

Так что, к счастью, дизайнеры браузеров разработали его для работы не так. Вместо этого вызов ( setTimeout(fn, 5000);) делается мгновенно . Это очень важно понимать: даже с задержкой в ​​5000 миллисекунд вызов этой функции выполняется мгновенно!Что будет дальше? Он удаляется из основного пространства. Но где это будет надеваться? Потому что мы не хотим полностью забывать об этом. Вы, должно быть, угадали: браузер слышит этот звонок и помещает его во второе место.

Теперь (эта часть тоже важна, так что обратите внимание) браузер отслеживает 5-секундную задержку, и как только она проходит, он смотрит на основное пространство, и "КОГДА ОНО ПУСТО" ставит fn();перезвоните на это . Вот как setTimeout работает мой дорогой!

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

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

Оба из этих двух самых популярных ответов неверны. Посмотрите описание MDN для модели параллелизма и цикла обработки событий, и должно стать ясно, что происходит (этот ресурс MDN является настоящим украшением). И просто используя setTimeout может добавлять неожиданные проблемы в ваш код в дополнение к "решению" этой маленькой проблемы.

Здесь на самом деле происходит не то, что "браузер может быть еще не совсем готов, потому что параллелизм" или что-то на основе "каждая строка - это событие, которое добавляется в конец очереди".

Jsfiddle, предоставленный DVK, действительно иллюстрирует проблему, но его объяснение не является правильным.

В его коде происходит то, что он первым присоединяет обработчик событий к click событие на #do кнопка.

Затем, когда вы действительно нажимаете кнопку, message создается со ссылкой на функцию обработчика событий, которая добавляется к message queue, Когда event loop достигает этого сообщения, он создает frame в стеке с вызовом функции для обработчика события click в jsfiddle.

И вот тут это становится интересным. Мы настолько привыкли думать о том, что Javascript является асинхронным, что мы склонны упускать из виду этот крошечный факт: любой кадр должен быть выполнен полностью, прежде чем может быть выполнен следующий кадр. Нет параллелизма, люди.

Что это значит? Это означает, что всякий раз, когда функция вызывается из очереди сообщений, она блокирует очередь до тех пор, пока сгенерированный ею стек не будет очищен. Или, в более общих чертах, он блокируется, пока функция не вернулась. И он блокирует все, включая операции рендеринга DOM, прокрутки и еще много чего. Если вам нужно подтверждение, просто попробуйте увеличить продолжительность длительной операции в скрипте (например, запустить внешний цикл еще 10 раз), и вы заметите, что во время его работы вы не можете прокручивать страницу. Если он работает достаточно долго, ваш браузер спросит вас, хотите ли вы завершить процесс, потому что он делает страницу не отвечающей. Кадр выполняется, а цикл событий и очередь сообщений застряли до его завершения.

Так почему же этот побочный эффект текста не обновляется? Потому что пока вы изменили значение элемента в DOM - вы можете console.log() его значение сразу после его изменения и увидеть, что оно было изменено (что показывает, почему объяснение DVK неверно) - браузер ожидает истощения стека (on функция обработчика для возврата) и, таким образом, сообщение для завершения, так что в конечном итоге он может приступить к выполнению сообщения, которое было добавлено средой выполнения в качестве реакции на нашу операцию мутации, и чтобы отразить эту мутацию в пользовательском интерфейсе.

Это потому, что мы на самом деле ждем завершения кода. Мы не сказали "кто-то получит это, а затем вызовет эту функцию с результатами, спасибо, и теперь я закончил с возвращением imma, делайте что угодно сейчас", как мы обычно делаем с нашим основанным на событиях асинхронным Javascript. Мы вводим функцию-обработчик события щелчка, мы обновляем элемент DOM, вызываем другую функцию, другая функция работает в течение длительного времени и затем возвращается, затем мы обновляем тот же элемент DOM, а затем возвращаемся из исходной функции, фактически опустошая стек. И тогда браузер может перейти к следующему сообщению в очереди, которое вполне может быть сообщением, сгенерированным нами путем запуска некоторого внутреннего события типа "on-DOM-mutation".

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

Почему setTimeout вещь работает тогда? Это происходит потому, что он эффективно удаляет вызов долго выполняющейся функции из своего собственного фрейма, планируя ее последующее выполнение в window контекст, так что он сам может немедленно вернуться и позволить очереди сообщений обрабатывать другие сообщения. И идея заключается в том, что сообщение "при обновлении" пользовательского интерфейса, которое было сгенерировано нами в Javascript при изменении текста в DOM, теперь опережает сообщение, помещенное в очередь для долго выполняющейся функции, поэтому обновление пользовательского интерфейса происходит до того, как мы заблокируем в течение долгого времени.

Обратите внимание, что а) долго выполняющаяся функция все еще блокирует все, когда она выполняется, и б) вы не гарантированы, что обновление пользовательского интерфейса действительно опережает его в очереди сообщений. В моем браузере Chrome за июнь 2018 года значение 0 не решает проблему, которую демонстрирует скрипка - 10 делает. Я на самом деле немного задушен этим, потому что мне кажется логичным, что сообщение об обновлении пользовательского интерфейса должно быть поставлено в очередь перед ним, так как его триггер выполняется перед планированием долгосрочной функции, которая будет запущена "позже". Но, возможно, в движке V8 есть некоторые оптимизации, которые могут помешать, или, может быть, мне просто не хватает понимания.

Хорошо, так в чем проблема с использованием setTimeout и что является лучшим решением для этого конкретного случая?

Во-первых, проблема с использованием setTimeout в любом обработчике событий, подобном этому, чтобы попытаться смягчить другую проблему, он склонен связываться с другим кодом. Вот реальный пример из моей работы:

Коллега, в неправильном понимании цикла событий, попытался "продвинуть" Javascript, используя некоторый код для отрисовки шаблонов. setTimeout 0 для его рендеринга. Его больше нет, чтобы спрашивать, но я могу предположить, что, возможно, он вставил таймеры для измерения скорости рендеринга (что было бы возвращением непосредственности функций) и обнаружил, что использование этого подхода приведет к невероятно быстрой реакции этой функции.

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

"Исправление" при написании нового обработчика событий, которое зависело от его логики, заключалось в том, чтобы setTimeout 0, Но это не исправление, это трудно понять, и неинтересно отлаживать ошибки, вызванные таким кодом. Иногда проблем никогда не бывает, иногда постоянно происходит сбой, а потом, опять же, иногда это работает и прерывается время от времени, в зависимости от текущей производительности платформы и того, что еще происходит в данный момент. Вот почему я лично советую не использовать этот хак (это хак, и мы все должны знать, что это так), если вы действительно не знаете, что делаете и каковы последствия.

Но что мы можем сделать вместо этого? Что ж, как указано в упомянутой статье MDN, либо разделите работу на несколько сообщений (если можете), чтобы другие сообщения, находящиеся в очереди, могли чередоваться с вашей работой и выполнялись во время ее выполнения, либо используйте веб-работника, который может запустить в тандеме с вашей страницей и возвращать результаты, когда закончите с ее расчетами.

О, и если вы думаете: "Ну, я не могу просто добавить обратный вызов в долговременную функцию, чтобы сделать ее асинхронной?", Тогда нет. Обратный вызов не делает его асинхронным, ему все равно придется выполнить долго выполняющийся код, прежде чем явно вызвать ваш обратный вызов.

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

изменить сейчас, что это 2015, я должен отметить, что есть также requestAnimationFrame()что не совсем то же самое, но достаточно близко к setTimeout(fn, 0) что стоит упомянуть.

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

Итак, у вас есть две функции:

var f1 = function () {    
   setTimeout(function(){
      console.log("f1", "First function call...");
   }, 0);
};

var f2 = function () {
    console.log("f2", "Second call...");
};

а затем позвоните им в следующем порядке f1(); f2(); просто чтобы увидеть, что второй исполнен первым.

И вот почему: невозможно иметь setTimeout с задержкой 0 миллисекунд. Минимальное значение определяется браузером и не равно 0 миллисекундам. Исторически браузеры устанавливают этот минимум на 10 миллисекунд, но спецификации HTML5 и современные браузеры устанавливают его на 4 миллисекунды.

Если уровень вложенности больше 5, а время ожидания меньше 4, увеличьте время ожидания до 4.

Также из Mozilla:

Для реализации времени ожидания 0 мс в современном браузере вы можете использовать window.postMessage(), как описано здесь.

PS информация взята после прочтения следующей статьи.

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

Другая вещь, которую это делает, это выдвигает вызов функции в конец стека, предотвращая переполнение стека, если вы рекурсивно вызываете функцию. Это имеет эффект while цикл, но позволяет движку JavaScript запускать другие асинхронные таймеры.

Некоторые другие случаи, когда setTimeout полезен:

Вы хотите разбить длительный цикл или вычисление на более мелкие компоненты, чтобы браузер не зависал и не говорил "Сценарий на странице занят".

Вы хотите отключить кнопку отправки формы при нажатии, но если отключить кнопку в обработчике onClick, форма не будет отправлена. setTimeout со временем ноль делает трюк, позволяя завершить событие, начать отправку формы, после чего ваша кнопка может быть отключена.

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

Ответы о циклах выполнения и рендеринге DOM до завершения какого-либо другого кода являются правильными. Нулевое время ожидания в JavaScript помогает сделать код псевдо-многопоточным, хотя это не так.

Я хочу добавить, что значение BEST для кросс-браузерного / кроссплатформенного тайм-аута на ноль секунд в JavaScript на самом деле составляет около 20 миллисекунд вместо 0 (нуля), потому что многие мобильные браузеры не могут регистрировать тайм-ауты менее 20 миллисекунд из-за ограничений времени на чипах AMD.

Кроме того, длительные процессы, которые не связаны с манипулированием DOM, теперь должны отправляться веб-работникам, поскольку они обеспечивают истинное многопоточное выполнение JavaScript.

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

  1. setTimeout() вызывает событие быть асинхронным, поэтому выполняется после всего синхронного кода, давая вашему элементу больше времени для загрузки. Асинхронные обратные вызовы, такие как обратный вызов в setTimeout() помещаются в очередь событий и помещаются в стек циклом событий после того, как стек синхронного кода пуст.
  2. Значение 0 для мс в качестве второго аргумента в функции setTimeout() часто немного выше (4-10 мс в зависимости от браузера). Это немного больше времени, необходимого для выполнения setTimeout() обратные вызовы вызваны количеством 'тиков' (где тик помещает обратный вызов в стек, если стек пуст) цикла событий. Из-за производительности и времени автономной работы количество тактов в цикле событий ограничено определенной величиной менее 1000 раз в секунду.

setTimout в 0 также очень полезен в шаблоне установки отложенного обещания, которое вы хотите вернуть сразу:

myObject.prototype.myMethodDeferred = function() {
    var deferredObject = $.Deferred();
    var that = this;  // Because setTimeout won't work right with this
    setTimeout(function() { 
        return myMethodActualWork.call(that, deferredObject);
    }, 0);
    return deferredObject.promise();
}

Javascript является однопоточным приложением, поэтому не позволяет одновременно запускать функции, поэтому для достижения этого используются циклы событий. Так что именно то, что setTimeout(fn, 0) делает это, помещается в квест задачи, который выполняется, когда ваш стек вызовов пуст. Я знаю, что это объяснение довольно скучно, поэтому я рекомендую вам просмотреть это видео, оно поможет вам, как все работает в браузере. Проверьте это видео: - https://www.youtube.com/watch?time_continue=392&v=8aGhZQkoFbQ

Другие вопросы по тегам