Разъяснение по синхронным и асинхронным обратным вызовам в javascript - PullRequest
1 голос
/ 27 января 2020

В настоящее время я работаю над концепцией обратных вызовов и асинхронного программирования в javascript. Для этого я прочитал соответствующие главы в книге "JavaScript", написанной Филиппом Аккерманом (ISBN: 937-3-8362-5696-4). Но у меня есть проблемы с пониманием термина асинхронный, применяемого к обратным вызовам, используемым в примерах книги.

В настоящее время я понимаю, что могу написать синхронные обратные вызовы, например:

function synchronousCallback(text, callback) {
    //other code
    callback(text);
}
synchronousCallback("End of function", console.log);

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

function asyncFunction(callbackFunction) {
    //some code
    console.log('Before callback');
    callbackFunction();
    console.log('After callback');
    //some more code
}
function callbackFunction() {
    console.log('called callback');
}
asyncFunction(callbackFunction);

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

На мой взгляд, обратный вызов является асинхронным при использовании с setTimeout () или setInterval () .

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

Спасибо за любую помощь или разъяснение

Ответы [ 2 ]

1 голос
/ 27 января 2020

Нет ничего асинхронного в приведенном вами примере.

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

Либо книга неверна, либо вы недостаточно выразили все в "подобном" примере, который она дала.

0 голосов
/ 03 марта 2020

Я уже упоминал разницу между синхронным и асинхронным поведением в JavaScript с этим ответом на ваш другой вопрос. Я постараюсь дать вам больше подробностей с этим ответом.

Там я рекомендовал вам посмотреть пару выступлений на эту тему. доклад Филиппа Робертса, другой доклад Джейка Арчибальда или блог Джейка , который объясняет то же самое. Я постараюсь обобщить все это.

Весь код JavaScript является синхронным и выполняется в одном потоке. Это означает, что есть один стек вызовов, и он может делать что-то одно за раз. Чтобы лучше понять время выполнения JavaScript, взгляните на это изображение, взятое с MDN .

Event loop

Давайте попробуем go через ваш второй пример, чтобы увидеть, что происходит. Когда вызывается asyncFunction(), он помещается в стек (Джейк называет их задачами, но на основе образа MDN они являются кадрами). Затем вызывается console.log('Before callback') и он помещается в стек поверх текущего кадра. Так что теперь есть console.log сверху и asyncFunction снизу.

console.log записывает эту строку в консоль. Затем он удаляется (выталкивается) из стека. asyncFunction теперь на вершине стека. Теперь вызывается callbackFunction() и он помещается в стек, который затем вызывает console.log('called callback'), который также помещается в стек.

Теперь в стеке есть три функции: asyncFunction в внизу, callbackFunction и console.log вверху. Когда console.log заканчивает свою работу, он выталкивается из стека, и теперь callbackFunction также заканчивается, и тот также извлекается из стека. Теперь вызывается console.log('After callback'), помещается в стек и извлекается после выполнения, что означает, что asyncFunction завершено и может быть извлечено из стека.

Здесь все заканчивается, стек пуст, нет больше кадров на нем. Исходя из этого и разговоров по ссылке выше, в этом примере нет ничего асинхронного. Шаг за шагом выполняется JS во время выполнения, и здесь не делаются асинхронные переходы. Но как нам достичь параллелизма в JS и нужен ли он нам? Цитируя Филиппа:

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

Именно поэтому мы можем использовать setTimeout(() => { doSomething(); }, 5000) который будет ждать 5 (000 тысяч милли) секунд, не блокируя (замораживая) веб-страницу в течение этого времени. Что происходит, когда вызывается setTimeout? Браузер запускает другой поток, который работает в параллельно . Работа потока состоит в том, чтобы ждать только 5 секунд. Но теперь становится интересно, что происходит, когда время истекло.

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

Но когда сообщения обработаны? Ну, только после того, как никакие кадры (задачи) не сложены. Это означает, что сообщения ожидают очистки стека после завершения всех кадров, чтобы их можно было обработать. Сообщения обрабатываются по одному за один l oop. Как только сообщение принимается в качестве задачи, оно становится обычным кодом JS, который выполняется в том же потоке с теми же правилами для стека проталкивания и выталкивания. Любые другие потенциальные сообщения, находящиеся в очереди в это время, должны ждать обработки текущего сообщения / кадра.

setTimeout и setInterval являются частями WebAPI s. Многие (если не все) имеют асинхронные обратные вызовы, и некоторые примеры включают: события DOM, события XHR, события Fetch, веб-работники, веб-сокеты, Promises, обратные вызовы MutationObserver и так далее. Последние два (Promises и MutationObservers) планируют задачи в другой очереди (очереди микрозадач), но они все еще асинхронны.

Разница между сообщениями, для которых задана очередь микрозадач и обычная (или макрозадача) очередь, состоит в том, что сообщения из очередей макрозадач принимаются по одному за событие l oop (что означает, что для всего l oop требуется до go между двумя сообщениями обрабатываются), в то время как сообщения из очередей микрозадач принимаются сразу после очистки стека. Это означает, что они имеют более высокий приоритет, чем те, которые находятся в очередях макросов. Для получения дополнительной информации, пожалуйста, смотрите / читайте доклад / блог Джейка.

На мой взгляд, обратный вызов является асинхронным при использовании с setTimeout () или setInterval ().

Да, но не только с setTimeout() или setInterval(). Если вы установите функцию обратного вызова на XMLHttpRequest onreadystatechange, она все равно будет вызываться асинхронно.

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

...