Вы правы в том, что nodejs работает в одном потоке. Вы можете начать отдельные процессы и поставить балансировку нагрузки перед этими процессами, чтобы использовать многоядерные процессоры, но это не реализовано в самом узле.
Однако узел не разворачивает поток для каждой асинхронной функции, с которой он сталкивается. Он помещает эти вызовы функций в цикл событий, который работает постоянно. Важно то, что цикл событий не блокирует, что означает, что порядок выполнения вызова функции не гарантируется - это причина, по которой обратные вызовы являются основой для всех программ node.js. Это означает, что, например, пока одна функция ожидает, что файл будет найден в файловой системе и прочитан в буфер, цикл событий не останавливает все, чтобы дождаться завершения и продолжения следующего вызова. Но это не многопоточное поведение. Контур события не выдает ничего параллельного, он просто постоянно переключается между задачами. Это просто многозадачность :)
В вашем примере while (true) блокирует цикл событий, потому что это не вызов функции асинхронного вызова. Не забывайте, что не все в javascript является асинхронным - только вызовы функций могут быть асинхронными, а не другими языковыми конструкциями, такими как циклы или условные обозначения. Таким образом, в вашем примере вы запускаете асинхронный файловый ридер, который добавляется в цикл событий и запускается. Хотя файловая система делает это, цикл событий не имеет ничего общего и переходит к сообщению console.log («before loop»). После этого вы блокируете цикл событий с while (true). Это означает, что цикл событий не может переместить его на удержание и больше перейти к файлообменнику.
Надеюсь, что это прояснит ситуацию.Основной вывод здесь заключается в том, что не все в javascript является или должно быть асинхронным, но большинство трудоемких операций, связанных с различными устройствами, такими как файловая система или сеть, реализованы неблокируемым способом. Функция Async имеет смысл только тогда, когда использует какой-то внешний компонент - вы бы не использовали функцию async square root, поскольку она блокировала бы цикл событий одинаково. Это причина, по которой node.js не рекомендуется для тяжелых вычислительных операций, таких как кодирование аудио/видео. Преимущество async просто не имеет смысла.
Магия обратных вызовов работает из-за закрытий. Когда вы передаете функцию 'readFile', она сохраняет эту функцию в активном состоянии и сохраняет все переменные, захваченные в закрытии. Позже узел может внезапно вызвать эту функцию (при условии, что вы вернулись в цикл событий узла), и с точки зрения программиста это очень удобно, потому что закрытие имеет переменные между вызовами. Вы можете вернуться из этого образца кода, который вы показали, потому что любые необходимые переменные останутся в живых и будут доступны из любых функций, которые в них нуждаются. – doug65536