Как происходит асинхронное выполнение Javascript? а когда не использовать оператор return?
// synchronous Javascript
var result = db.get('select * from table1');
console.log('I am syncronous');
// asynchronous Javascript
db.get('select * from table1', function(result){
// do something with the result
});
console.log('I am asynchronous')
Я знаю, что в синхронном коде console.log() выполняется после получения результата из db, тогда как в асинхронном коде console.log() выполняется до того, как db.get() извлекает результат.
Теперь мой вопрос: как происходит асинхронный код за кулисами и почему он не блокируется?
Я искал стандарт Ecmascript 5, чтобы понять, как работает асинхронный код, но не смог найти слово асинхронный во всем стандарте.
А из nodebeginner.org я также узнал, что мы не должны использовать оператор return, поскольку он блокирует цикл обработки событий. Но api nodejs и сторонние модули содержат операторы return везде. Так когда же следует использовать оператор return, а когда - нет?
Может кто-нибудь пролить свет на это?
2 ответа
Прежде всего, передача функции в качестве параметра говорит о том, что вызываемая вами функция должна вызывать эту функцию в будущем. Когда именно в будущем он будет вызван, зависит от характера того, что делает функция.
Если функция выполняет какие-то сетевые функции, а функция настроена на неблокирующую или асинхронную, то функция будет выполнена, сетевая операция будет запущена, и функция, которую вы вызвали, вернется сразу же, а остальная часть встроенного кода JavaScript после эта функция будет выполняться. Если вы вернете значение из этой функции, оно вернется сразу же, задолго до того, как функция, которую вы передали в качестве параметра, была вызвана (сетевая операция еще не завершена).
Между тем, работа в сети идет в фоновом режиме. Это отправка запроса, прослушивание ответа, а затем сбор ответа. Когда сетевой запрос завершен и ответ собран, ТО и только тогда исходная функция, которую вы вызвали, вызывает функцию, которую вы передали в качестве параметра. Это может произойти только через несколько миллисекунд или может занять несколько минут - в зависимости от того, сколько времени потребовалось для завершения сетевой операции.
Важно понимать, что в вашем примере db.get()
Вызов функции давно завершен, и код также последовательно выполняется. Не завершена внутренняя анонимная функция, которую вы передали в качестве параметра этой функции. Это сохраняется в закрытии функции javascript до тех пор, пока не завершится сетевая функция.
По моему мнению, одна вещь, которая смущает многих людей, это то, что анонимная функция объявляется внутри вашего вызова db.get и, по-видимому, является частью этого, и кажется, что когда db.get()
сделано, это будет сделано тоже, но это не так. Возможно, это выглядело бы не так, если бы это было представлено так:
function getCompletionfunction(result) {
// do something with the result of db.get
}
// asynchronous Javascript
db.get('select * from table1', getCompletionFunction);
Тогда, возможно, было бы более очевидно, что db.get вернется немедленно, и функция getCompletionFunction будет вызвана через некоторое время в будущем. Я не предлагаю вам писать так, а просто показываю эту форму как средство иллюстрации того, что на самом деле происходит.
Вот последовательность, которую стоит понять:
console.log("a");
db.get('select * from table1', function(result){
console.log("b");
});
console.log("c");
В консоли отладчика вы увидите следующее:
a
c
b
"а" происходит первым. Затем db.get() начинает свою работу, а затем сразу же возвращается. Таким образом, "с" происходит дальше. Затем, когда операция db.get() действительно завершится через некоторое время, произойдет "b".
Чтобы узнать, как работает асинхронная обработка в браузере, см. Как JavaScript обрабатывает ответы AJAX в фоновом режиме?
Ответ jfriend00 объясняет асинхронность, поскольку она довольно хорошо применима к большинству пользователей, но в своем комментарии вы, похоже, хотели узнать больше о реализации:
[…] Может ли кто-нибудь написать какой-нибудь псевдокод, объясняющий часть реализации спецификации Ecmascript для достижения такого рода функциональности? для лучшего понимания внутренних органов JS.
Как вы, вероятно, знаете, функция может хранить свой аргумент в глобальной переменной. Допустим, у нас есть список чисел и функция для добавления числа:
var numbers = [];
function addNumber(number) {
numbers.push(number);
}
Если я добавлю несколько номеров, пока я имею в виду тот же numbers
переменная, как и раньше, я могу получить доступ к номерам, которые я добавил ранее.
Реализации JavaScript, вероятно, делают нечто подобное, за исключением того, что они не убирают числа, а убирают функции (в частности, функции обратного вызова).
Цикл событий
В основе многих приложений лежит то, что известно как цикл обработки событий. По сути это выглядит так:
- цикл навсегда:
- получать события, блокируя, если не существует
- обрабатывать события
Допустим, вы хотите выполнить запрос к базе данных, как в вашем вопросе:
db.get("select * from table", /* ... */);
Чтобы выполнить этот запрос к базе данных, ему, вероятно, потребуется выполнить сетевую операцию. Поскольку сетевые операции могут занимать значительное количество времени, в течение которого процессор ожидает, имеет смысл подумать, что, возможно, нам следует вместо того, чтобы ждать, а не выполнять какую-либо другую работу, просто попросить нас сообщить, когда это будет сделано, чтобы мы могли сделать другие вещи в то же время.
Для простоты я буду делать вид, что отправка никогда не будет блокироваться / блокироваться синхронно.
Функциональность get
может выглядеть так:
- генерировать уникальный идентификатор для запроса
- отправить запрос (опять же, для простоты, если он не блокируется)
- убрать пару (идентификатор, обратный вызов) в глобальную переменную словарь / хеш-таблицу
Это все get
сделал бы; он не выполняет никаких битов приема и сам не отвечает за вызов вашего обратного вызова. Это происходит в процессе событий немного. Бит событий процесса может выглядеть (частично) так:
- Является ли событие ответом базы данных? если так:
- разобрать ответ базы данных
- найдите идентификатор в ответе в хэш-таблице, чтобы получить обратный вызов
- перезвонить с полученным ответом
Реальная жизнь
В реальной жизни все немного сложнее, но общая концепция не слишком отличается. Например, если вы хотите отправить данные, вам, возможно, придется подождать, пока в исходящих сетевых буферах операционной системы будет достаточно места, прежде чем вы сможете добавить свой бит данных. При чтении данных вы можете получить его несколькими частями. Бит событий процесса, вероятно, не является одной большой функцией, а сам по себе просто вызывает несколько обратных вызовов (которые затем отправляются на большее количество обратных вызовов и т. Д.)
Хотя детали реализации между реальной жизнью и нашим примером немного отличаются, концепция та же: вы запускаете "что-то делать", и обратный вызов будет вызываться через тот или иной механизм, когда работа будет завершена.