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

Цикл событий JavaScript

Цикл событий получил свое название из-за того, как он обычно реализуется; это модель времени выполнения, которая всегда перебирает события. В Javascript события — это подзадачи, которые ставятся в очередь для обработки. Возьмем, к примеру, простой цикл обработки событий, как показано в следующей функции:

while (queue.waitForEvent()) {
  queue.processNextEvent();
}

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

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

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

Асинхронное программирование

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

// we suppose we have a mongodb User object:
const filterUserByEmail = async(inputEmail) => {
    try {
        const user = await User.findOne({email: inputEmail});
        if(!user) {
            throw error;
        } else {
            ....
        }
    } catch(e) {
        console.log(e);
    };
};

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