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

Итак, вы приступаете к работе в Excel и через сорок минут распланировали свой ежемесячный бюджет. Вы звоните своему помощнику и говорите, чтобы они спустились на первый этаж и забрали вашу почту. А потом вы ждете… и ждете… и ждете. Через двадцать минут ваш ассистент возвращается, весь в поту. Лифты вышли из строя, и им только что пришлось спуститься и подняться на десять лестничных пролетов. Теперь вам нужно спешить на собрание, и ваши акционеры получат бюджет только после этого!

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

Поздравляем, новый бизнес-директор! Вы только что познакомились с концепцией под названием асинхронность.

Чтобы снова связать это с JavaScript, давайте попробуем проиллюстрировать предыдущий список задач в псевдокоде.

const investors = ["Warren Buffet", "Bill Gates", "Tony Stark", "another famous rich person"]
createBudget();
haveAssistantGetMail();
investors.forEach(investor => {
   emailBudgetTo(investor);
}

В этом примере haveAssistantGetMail () - асинхронная функция. Другими словами, JavaScript знает, что эта функция может занять некоторое время, поэтому пока она выполняется, JavaScript перейдет к следующей строке кода. Код в этом блоке будет фактически выполняться в следующем порядке: сначала запускается createBudget (). Затем JavaScript запускает запуск hasAssistantGetMail (). Пока это происходит, будет выполнен следующий блок кода с участием emailBudgetTo (). Официально haveAssistantGetMail () не завершает свою работу до тех пор, пока не будет запущен этот код. Таким образом, используя нашу метафору, наш помощник не вернется в наш офис, пока мы не отправим всем по электронной почте бюджет. Примеры реальных асинхронных функций в JavaScript включают fetch () и setTimeout ().

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

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

const investors = [];
createBudget();
haveAssistantGetMail();
investors.forEach(investor => {
   emailBudgetTo(investor);
}
function haveAssistantGetMail() {
   getListOfInvestors();
}

Поскольку мы уже знаем, что haveAssistantGetMail () является асинхронным и не завершит свою работу до тех пор, пока не завершится остальная часть нашего кода, мы можем экстраполировать, что emailBudgetTo () будет срабатывать… ровно ноль раз, поскольку он все равно будет отвечать на пустой множество инвесторов. Этот блок (псевдо-) кода не приведет к выполнению всех наших задач!

Есть несколько способов обойти это, чтобы наш код работал должным образом. Один из самых актуальных - использовать .then ():

const investors = [];
createBudget();
haveAssistantGetMail()
  .then(){
     investors.forEach(investor => {
       emailBudgetTo(investor);
     }
  };
function haveAssistantGetMail() {
   getListOfInvestors();
}

.then () гарантирует, что блок кода внутри не будет выполняться до тех пор, пока асинхронная функция, за которой он следует, не будет завершена. В этом случае код, по сути, действует так, как если бы он вообще не был асинхронным: блок кода emailBudgetTo () не сработает до тех пор, пока не завершится выполнение haveAssistantGetMail (). Этот формат позволяет нам ждать отправки электронных писем до тех пор, пока не будет получен наш список инвесторов. Конечно, тем самым мы еще раз убедились, что наша работа займет больше часа… к счастью, в реальном Javascript большая часть кода запускается за миллисекунды!

Чтобы узнать больше об асинхронности:

Асинхронный JavaScript в документации Mozilla JS

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

Асинхронность и обещания на JavaScript.info