Я знаю, что функция обратного вызова работает асинхронно, но почему?

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

Рассмотрим простой асинхронный ввод-вывод в node.js

 var fs = require('fs');
 var path = process.argv[2];

  fs.readFile(path, 'utf8', function(err,data) {
   var lines = data.split('\n');
   console.log(lines.length-1);
  });

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

Это не конкретный вопрос о node.js, он об общей концепции обратного вызова на каждом языке программирования.

EDIT:

Вероятно, приведенный ниже пример не лучший. Поэтому не будем рассматривать этот фрагмент кода node.js. Я прошу в целом - что делает трюк, который программа продолжает выполнять при вызове функции обратного вызова. Что такое синтаксис что делает концепцию обратного вызова неблокирующей?

Спасибо заранее!

Ответ 1

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

setTimeout(function(){
    console.log("this is async");
}, 100);

или это может быть синхронно, например:

an_array.forEach(function(x){
    console.log("this is sync");
});

Итак, как вы можете узнать, будет ли функция вызывать обратный вызов синхронно или асинхронно? Единственный надежный способ - прочитать документацию.

Вы также можете написать тест, чтобы выяснить, доступна ли документация:

var t = "this is async";
some_function(function(){
    t = "this is sync";
});

console.log(t);

Как работает асинхронный код

Javascript, по сути, не имеет никакой функции, чтобы сделать функции асинхронными. Если вы хотите написать асинхронную функцию, у вас есть два варианта:

  1. Используйте другую асинхронную функцию, такую как setTimeout или веб-работники, для выполнения вашей логики.

  2. Напишите это на C.

Что касается того, как кодированные функции C (такие как setTimeout) реализуют асинхронное выполнение? Все это связано с циклом событий (или главным образом).

Цикл событий

Внутри веб-браузера есть этот фрагмент кода, который используется для работы в сети. Изначально сетевой код мог загружать только одну вещь: саму HTML-страницу. Когда Мозаика изобрела <img> сетевой код эволюционировал для загрузки нескольких ресурсов. Затем Netscape реализовал прогрессивную визуализацию изображений, им пришлось сделать сетевой код асинхронным, чтобы они могли рисовать страницу до загрузки всех изображений и обновлять каждое изображение постепенно и индивидуально. Это начало цикла событий.

В основе браузера находится цикл обработки событий, который развился из асинхронного сетевого кода. Поэтому неудивительно, что он использует примитив ввода/вывода в качестве своего ядра: select() (или что-то подобное, например poll, epoll и т.д. В зависимости от ОС).

Функция select() в C позволяет вам ожидать нескольких операций ввода-вывода в одном потоке без необходимости создавать дополнительные потоки. select() выглядит примерно так:

select (max, readlist, writelist, errlist, timeout)

Чтобы он readlist ввода-вывода (из сокета или диска), вы добавили бы дескриптор файла в список readlist и он будет возвращаться, когда есть данные, доступные на любом из ваших каналов ввода-вывода. Как только он вернется, вы можете продолжить обработку данных.

Интерпретатор javascript сохраняет ваш обратный вызов, а затем вызывает функцию select(). Когда select() возвращает, интерпретатор выясняет, какой обратный вызов связан с каким каналом ввода/вывода, а затем вызывает его.

Удобно, что select() также позволяет вам указать значение timeout. Тщательно управляя временем timeout переданным select() вы можете вызвать обратные вызовы в будущем. Вот как реализованы setTimeout и setInterval. Интерпретатор хранит список всех тайм-аутов и вычисляет, что ему нужно передать в качестве timeout для select(). Затем, когда select() возвращается в дополнение к выяснению, есть ли какие-либо обратные вызовы, которые должны быть вызваны из-за операции ввода-вывода, интерпретатор также проверяет любые истекшие тайм-ауты, которые должны быть вызваны.

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

Node.js также порождает потоки при работе с файловым/дисковым вводом/выводом. Когда операция ввода/вывода завершается, она связывается с главным циклом событий, чтобы вызвать выполнение соответствующих обратных вызовов.


Надеюсь, это ответит на ваш вопрос. Я всегда хотел написать этот ответ, но был занят, чтобы сделать это ранее. Если вы хотите узнать больше о неблокирующем программировании ввода/вывода в CI, рекомендуем вам прочитать это: http://www.gnu.org/software/libc/manual/html_node/Waiting-for-I_002fO.html

Для получения дополнительной информации см. Также:

Ответ 2

Обратный вызов не обязательно асинхронный. Выполнение полностью зависит от того, как fs.readFile решает обработать параметр функции.

В JavaScript вы можете выполнять функцию асинхронно, используя, например, setTimeout.

Обсуждение и ресурсы:

Как node.js реализует неблокирующий ввод-вывод?

Concurrency модель и цикл событий

Wikipedia:

Существует два типа обратных вызовов, отличающихся тем, как они управляют потоком данных во время выполнения: блокирование обратных вызовов (также называемых синхронными обратными вызовами или просто обратными вызовами) и отложенными обратными вызовами (также называемыми асинхронными обратными вызовами).

Ответ 3

Прежде всего, если что-то не является Async, это означает, что он блокирует. Таким образом, бегун javascript останавливается на этой строке до тех пор, пока эта функция не закончится (что будет делать readFileSync).

Как мы все знаем, fs является библиотекой IO, поэтому такие вещи требуют времени (скажите, что аппаратное обеспечение для чтения некоторых файлов не является чем-то сразу), поэтому имеет смысл, что все, что не требует только CPU, это асинхронно, потому что для этого требуется время, и не нужно замораживать остальную часть кода для ожидания другой части аппаратного обеспечения (в то время как CPU неактивен).

Надеюсь, это решает ваши сомнения.