JS: Асинхронное программирование
Теория: Промисы (Promise)
При всех его преимуществах, асинхронный код очень сложен в анализе. Буквально несколько вложенных колбеков с параллельным выполнением операций — и все, уже практически невозможно разобраться в происходящем. Страшно представить себе большую программу, в которой асинхронно все. Тысяча-другая строк кода — и ни один человек не сможет понять его.
С самого начала разработчики понимали ограниченность подхода с колбеками, но понадобилось немало времени до того, как в JavaScript появилась альтернатива — Promises. Промисы меняют способ организации кода, не добавляя нового синтаксиса. При правильном использовании они позволяют «выпрямить» асинхронный код и сделать его предельно плоским и последовательным.
Большая часть современного JavaScript кода пишется на промисах, а колбеки уходят в прошлое. Например, разработчики Node.js внедрили промисы практически во все встроенные модули. Функции для работы с файловой системой, построенные на промисах, доступны через свойство promises модуля fs. Сравните примеры:
Технически промис — это специальный объект, который отслеживает асинхронную операцию и хранит внутри себя её результат. Он возвращается всеми асинхронными функциями, построенными на промисах.
Очень важно понимать, что промис — это не результат асинхронной операции. Это объект, который отслеживает выполнение операции. Операция по-прежнему асинхронна и выполнится когда-нибудь потом.
Как получить результат выполнения асинхронной операции? Снаружи — никак, это просто невозможно. Но промис можно «продолжить», используя метод then(), в который нужно передать колбек-функцию. Параметром этого колбека и будет тот самый результат асинхронной операции
Колбек именно передается внутрь then(), а не вызывается. Вызов делает уже сам промис тогда, когда выполнится асинхронная операция.
Независимо от содержимого колбек-функции, вызов then() всегда возвращает новый промис. А возврат колбек-функции становится доступным как параметр колбека следующего then(). Именно такая организация промисов позволяет строить цепочки без необходимости вкладывать вызовы друг в друга, тем самым избегая Callback Hell.
А что, если колбек-функция вернет не просто значение, а промис? Тогда параметром следующего then() становится результат выполнения этого промиса. Иначе промисы были бы бессмысленны. Пример:
Использование промисов внутри любой функции автоматически делает эту функцию асинхронной. Она больше не может вызываться как обычная синхронная функция, так как в таком случае невозможно воспользоваться результатом ее работы, дождаться выполнения операции или узнать об ошибках:
Как только в коде появляется асинхронность, код должен менять свою структуру. В случае колбеков он становится вложенным, в случае промисов весь код превращается в непрерывную цепочку промисов.
Неправильное использование промисов
Главное преимущество промисов перед колбеками в том, что с их помощью асинхронный код становится немного похож на синхронный. Видно цепочку вызовов, и она не растет вглубь. По крайней мере в теории. На практике же, промисы используются не всегда правильно. Посмотрите на код:
Несмотря на то, что здесь используются промисы, код выглядит даже сложнее чем с колбеками. Проблема в том, как построены вызовы. Продолжение цепочки идет не от верхнего промиса, а от каждой последующей асинхронной операции. В теории промисы действительно бывают вложенными, но только там, где по-другому никак. В любой другой ситуации код должен быть плоским и простым:





