Как происходит асинхронное выполнение Javascript?а когда не использовать инструкцию return? - PullRequest
37 голосов
/ 18 августа 2011
// 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, а когда - нет?

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

Ответы [ 2 ]

29 голосов
/ 18 августа 2011

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

Если функция выполняет какие-то сетевые функции, а функция настроена на неблокирующую или асинхронную, то функция будет выполнена, сетевая операция будет запущена, и функция, которую вы вызвали, сразу же вернется, а остальная часть вашего встроенного 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 в фоновом режиме?

17 голосов
/ 03 июля 2013

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

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

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

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

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

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

Цикл событий

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

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

Допустим, вы хотите выполнить запрос к базе данных, как в вашем вопросе:

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

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

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

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

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

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

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

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

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

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

...