Как выполняется асинхронное выполнение Javascript? и когда не использовать оператор возврата?

// 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, поскольку он блокирует цикл события. Но nodejs api и сторонние модули содержат операторы return везде. Итак, когда должен использоваться оператор return, а когда он не должен?

Может кто-нибудь пролить свет на это?

Ответ 1

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

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

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

Важно понимать, что в вашем примере вызов функции 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

"a" происходит первым. Затем db.get() начинает свою работу и сразу же возвращается. Таким образом, происходит "c". Затем, когда операция db.get() на самом деле завершается в будущем, происходит "b".


Для некоторого чтения о том, как обработка async работает в браузере, см. Как JavaScript обрабатывает ответы AJAX в фоновом режиме?

Ответ 2

jfriend00 answer объясняет асинхронность, как это применимо к большинству пользователей, но в вашем комментарии вам, похоже, нужна дополнительная информация о реализации:

[...] Может ли какой-либо орган написать некоторый псевдокод, объясняя часть реализации спецификации Ecmascript для достижения такой функциональности? для лучшего понимания внутренних компонентов JS.

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

var numbers = [];
function addNumber(number) {
    numbers.push(number);
}

Если я добавлю несколько чисел, пока я имею в виду одну и ту же переменную numbers, как и раньше, я могу получить доступ к ранее добавленным номерам.

JavaScript-реализации, вероятно, делают что-то подобное, за исключением того, что они не убирают номера, они убирают функции (в частности, функции обратного вызова).

Цикл событий

В основе многих приложений лежит так называемый цикл событий. Он выглядит примерно так:

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

Предположим, вы хотите выполнить запрос базы данных, например, в своем вопросе:

db.get("select * from table", /* ... */);

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

Для простоты сделаю вид, что отправка никогда не будет блокировать/останавливаться синхронно.

Функциональность get может выглядеть так:

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

Что все get будет делать; он не принимает никакого принимающего бита, и сам он не отвечает за вызов вашего обратного вызова. Это происходит в бит событий процесса. Бит событий процесса может выглядеть (частично) следующим образом:

  • является ли событие ответом базы данных? если так:
    • проанализировать ответ базы данных
    • найдите идентификатор в ответе в хеш-таблице, чтобы получить обратный вызов
    • вызов обратного вызова с полученным ответом

Реальная жизнь

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

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