Asyn c vs Syn c программирование - считается ли это стилем или предлагает более мощные возможности программирования? - PullRequest
0 голосов
/ 06 мая 2020

Асинхронное программирование становится все более популярным, особенно в области веб-разработки. Начальная точка всегда указывается в строках «Это предотвращает зависание пользовательского интерфейса et c et c». Однако, насколько я понимаю, функция, которая вызывается в асинхронном обратном вызове, будет выполняться в моем основном потоке в течение того же времени, в течение которого она выполнялась бы раньше, что означает, что если бы функция блокировки вызывалась асинхронно или синхронно, пользовательский интерфейс был бы тем же. Возьмем, например,

Синхронный


console.log("I will print")
function wait(ms) {
  var start = Date.now(),
      now = start;
  while (now - start < ms) {
    now = Date.now();
  }
}

wait(5000); 
console.log("I will not print, until after 5 seconds, due to synchronous execution")

Асинхронный

console.log("I will print")
setTimeout(() => {
    console.log("I will print after the callback has been called asynchronously (~3000ms)")
    wait(5000); 
    console.log("I will print after 5 seconds")
}, 3000)
console.log("I will also print immediately")

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

Итак, мой вопрос: почему асинхронное программирование считается в последнее время очень популярным, это просто как предпочтительное программирование стиль ? Предлагает ли он какие-то особые способности, с которыми синхронное программирование может столкнуться с трудностями?

1 Ответ

0 голосов
/ 09 мая 2020

В приведенном выше коде у вас есть функция сна, которая является синхронной и блокирующей. Поэтому всякий раз, когда вызывается ваша функция, она блокирует событие 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. Надеюсь, это было полезно.

...