В приведенном выше коде у вас есть функция сна, которая является синхронной и блокирующей. Поэтому всякий раз, когда вызывается ваша функция, она блокирует событие l oop и останавливает выполнение будущего кода. Если вы вызовете его немедленно, как в первом примере, l oop будет немедленно заблокирован, а пользовательский интерфейс зависнет. Если вы заключите его в setTimeout
, как во втором примере, вы просто откладываете проблему. Оба ваших примера демонстрируют синхронный код.
setTimeout
будет ждать выполнения любого содержащегося в нем кода, такого как ваша синхронная функция сна, до конца текущего цикла l oop события. Таким образом, операторы console.log
и любой другой код, который у вас есть после него, будут выполняться сначала, затем setTimeout
будет выполняться в конце l oop, а затем пользовательский интерфейс будет заблокирован. Чтобы продемонстрировать правильное сравнение с асинхронным кодом, давайте определим синхронную версию вашей функции сна и асинхронную версию:
// synchronous sleep function that blocks the event loop
const SyncSleep = (ms) => {
const start = Date.now(),
let now = start;
while (now - start < ms) {
now = Date.now();
}
console.log('Sync timer ended.')
}
// async sleep function that does not block event loop
const asyncSleep = (ms) =>
new Promise((resolve) => setTimeout(resolve, ms))
.then(() => console.log("Async timer finished."))
Если мы запустим синхронный таймер, мы увидим следующий вывод:
syncSleep(1000)
console.log('First.')
syncSleep(1000)
console.log('Second.')
syncSleep(1000)
console.log('Third.')
// Output:
// Sync timer ended.
// First.
// Sync timer ended.
// Second.
// Sync timer ended.
// Third.
Обратите внимание, что каждый раз, когда вызывается syncSleep
, он должен полностью ждать 1000 мс, log "Sync timer ended."
, а затем, наконец, перейти к следующей строке, чтобы log "First."
. Когда он дойдет до последнего журнала "Third"
, он только что потратит 3 секунды и некоторые изменения на то же событие l oop, что очень расточительно и крайне нежелательно. Сравните это со следующим:
asyncSleep(1000)
console.log('First.')
asyncSleep(1000)
console.log('Second.')
asyncSleep(1000)
console.log('Third.')
// Output:
// First.
// Second.
// Third.
// Async timer ended.
// Async timer ended.
// Async timer ended.
Основной поток в этом случае не блокируется, поэтому весь код, определенный для события l oop, выполняется без задержки. Вот порядок событий:
Он переходит к первому asyncSleep
, видит свое обещание, которое не выполняется до более позднего времени, поэтому он помещает его в очередь обратного вызова и переходит к следующей строке. . Затем он регистрирует "First."
, первый журнал, который вы видите в выходных данных, потому что предыдущий таймер еще не завершил работу и, следовательно, ничего не зарегистрировал. Событие l oop переходит к следующему asyncSleep
, видит свое обещание, поэтому оно также бросает его в очередь обратного вызова, а затем переходит на следующую строку. Следующая строка выводит "Second."
Затем следующая строка бросает последний таймер в очередь обратного вызова. Наконец, последняя строка выводит "Third."
. Весь этот цикл события l oop завершился за миллисекунды, по сравнению с последним примером, где l oop пришлось ждать, пока все не завершится sh, прежде чем завершить хотя бы один цикл вокруг события l oop.
Теперь, еще несколько циклов события l oop позже, первый таймер в очереди обратного вызова завершается через 1 секунду, поэтому обещание разрешается, событие l oop сбрасывает его, и выполняется оператор журнала таймера : "Timer finished."
. Следующее путешествие вокруг события l oop делает то же самое, проверяет очередь обратных вызовов, видит, что следующий таймер завершен, и снова выводит: Timer finished.
.
Теперь все это имеет смысл? В первом примере с синхронным таймером код будущих таймеров и операторов console.log
не мог выполняться, пока не завершились все предыдущие таймеры. В вашем случае пользовательский интерфейс был заблокирован. Во втором случае таймер обрабатывался асинхронно в очереди обратного вызова, и событие l oop могло проходить себя свободно. В результате все таймеры можно было обрабатывать, не дожидаясь друг друга. В конце концов, синхронный код выполнялся за 3 секунды, а асинхронный код - за 1 секунду. Измените каждый из этих таймеров на 3 секунды, и первая программа syn c завершится за 9 секунд, а вторая программа asyn c займет всего 3 секунды, экспоненциальная разница во времени.
Конечно, вы затем может ожидать таймеры asyn c, и тогда обе программы в конечном итоге займут 3 секунды в исходном примере. Однако версия asyn c остается превосходной, потому что ее таймеры обрабатываются в очереди обратного вызова, а не в событии l oop. Таким образом, в то время как поток обеих программ по-прежнему продолжается в стиле «подождите 1 секунду, войдите Timer ended.
, затем войдите First.
», версия asyn c сможет делать другие вещи, пока ожидает таймеры. до финиша sh. В моих комментариях выше эти другие вещи могут обрабатывать события в отдельном прослушивателе событий. В вашем случае он обрабатывает события пользовательского интерфейса. Таким образом, если у вас есть прослушиватель событий на кнопке, которая регистрирует «нажатие кнопки» каждый раз, когда ее нажимают, и вы ждете, пока эти таймеры завершат sh в очереди обратного вызова, кнопка все равно будет регистрировать эти операторы, и страница будет быть полностью отзывчивым. Не так в первом примере с SyncTimer
. Надеюсь, это было полезно.