что такое промисы js
Промисы на примерах из жизни
Поговорим о промисах простыми словами
Промисы простыми словами
Представьте это как разговор между двумя людьми:
Алекс: Эй, мистер Промис! Можешь сбегать в магазин и принести мне itemA для блюда, которое мы приготовим сегодня вечером?
Промис: Отличная мысль!
Алекс: Пока ты бегаешь, я подготовлю itemB (асинхронная операция). Только обязательно скажи, нашел ли ты itemA (возвращаемое промисом значение).
Промис: А если тебя не будет дома, когда я вернусь?
Алекс: Тогда отправь мне смску, что ты вернулся и принес item для меня (успешный колбэк). Если ты не найдешь, позвони мне немедленно (неуспешный колбэк).
Промис: Отлично! Увидимся позже!
Определение
Промис — это объект, представляющий окончательное завершение или сбой асинхронной операции. По сути, промис — это возвращаемый объект, к которому прикрепляется колбэк, вместо его передачи в функцию.
Промис в JS
Давайте сначала поговорим о JavaScript и его параллелизме. JavaScript является однопоточным. Всё происходит в той последовательности, в которой написано, но асинхронные операции происходят в порядке их завершения.
Что, по вашему мнению, выведется в консоль в следующем примере?
В типичном веб-приложении может выполняться множество асинхронных операций, таких как загрузка изображений, получение данных из JSON, обращение к API и других.
Теперь рассмотрим, как создать промис в JavaScript:
Пример
Чтобы полностью понять концепцию промисов, создадим приложение, которое загрузит изображение. Если изображение загружено, оно будет отображено, иначе будет выводится ошибка.
Сначала создадим promise с XMLHttpRequest :
Мы сделали это! Неплохо, да?
А теперь сделай несколько промисов сам! Давай 🙂
Дополнительные материалы
Вот некоторые статьи, которые показались мне очень полезным в процессе обучения:
Промисы в JavaScript для чайников
Промисы в JavaScript на самом деле совсем не сложные. Тем не менее, многие люди находят затруднительным их понимание с первых моментов изучения. Поэтому, снизу всё будет описано способом для начинающих, чтобы читающий понял материал полностью и объемно. Помните, что промисы лежат в основе async/await и их понимание, можно сказать, что обязательно для работы с асинхронным JavaScript.
Понимание промисов
Итак, вкратце про промисы: “Представьте, что вы ребенок. Ваша мама обещает вам, что вы получите новый телефон на следующей неделе.”
Вы не знаете, получите ли вы его до следующей неделе. Ваша мама может купить вам совершенно новый телефон, а может просто этого не сделать, к примеру, потому что она будет не в настроении 🙁
Это и есть промис. От английского promise — обещать. Небольшое уточнение, пожалуйста, не усложняйте понимание другим с произношением, так как во всём русскоязычном мире принято говорить “промис” в случае с JavaScript.
Итак, у промиса есть 3 состояния. Это:
1. Промис в состоянии ожидания ( pending ). Когда вы не знаете, получите ли вы мобильный телефон к следующей неделе или нет.
2. Промис решен ( resolved ). Вам реально купят новый телефон.
3. Промис отклонен ( rejected ). Вы не получили новый мобильный телефон, так как всё-таки, мама была не в настроении.
Создание промиса
Давайте переведем все это в JavaScript.
Код выше довольно выразителен и говорит сам за себя.
3. Тут у нас стандартный синтаксис для определения нового промиса, как в MDN документации. То есть синтаксис промиса выглядит таким образом.
Применяем промисы
Теперь у нас есть промис, давайте применим его:
Давайте запустим этот пример и увидим результат!
Цепочки промисов
Да, в промисах есть цепочки.
Давайте представим, что вы ребенок и обещали своему другу, что покажете ему новый телефон, когда вам его купят. Это будет ещё один промис.
Вот так легко связывать промисы.
Промисы и асинхронность
Промисы асинхронны. Давайте выведем сообщение перед и после вызовом промиса.
Какова последовательность ожидаемого вывода? Возможно вы ожидали.
Но на самом деле вывод будет таким:
Почему? Потому что жизнь (или JS) никого не ждёт.
Промисы в ES5, ES6/2015, ES7/Next
ES 5 — поддерживают почти все браузеры. Демо код работает в ES5 среде (Все основные браузеры + NodeJS), если бы вы подключили библиотеку промисов Bluebird. Почему так? Потому что ES5 не поддерживает промисы из коробки. Другая знаменитая библиотека промисов это Q, от Криса Коваля.
1. Всегда, когда вам нужно возвратить промис в функцию, вы ставите спереди async к этой функции. Для примера, async function showOff(phone)
3. Используйте try < … >catch(error) < … >, чтобы словить ошибку промиса, отклоненную промисом.
Почему промисы и когда их использовать?
Зачем они нам нужны? Как выглядел мир до промисов? Перед ответом на эти вопросы, давайте вернемся к основам.
Нормальная функция против асинхронной.
Давайте посмотрим на эти два примера, каждый пример делает сложение двух чисел, один пример с нормальной функцией, другой с удаленной.
Нормальная функция для сложения чисел.
Асинхронная функция для сложения двух чисел:
Если вы сложите числа нормальной функцией, то вы сразу же получите результат. Тем не менее, если в вашем случае нужен удаленный запрос для получения результата, то вам нужно подождать, тут вы не сможете получить результат мгновенно.
Или таким способом вы вообще не можете знать — получите ли вы результат, потому что сервер может просто упасть, тормознуть с ответом и т. п. Вам не нужно, чтобы весь процесс был заблокирован, пока вы ждете результат.
Вызов API, скачивание файлов, чтение файлов — всё это те обычные async операции, которые вы можете выполнять.
Мир до промисов — колбэки.
Должны ли мы использовать промисы для каждого асинхронного запроса? Нет. До промисов, мы используем колбэки. Колбэки это просто функция, которую вы вызываете, когда получаете отдаваемый результат. Давайте модифицируем предыдущий пример, чтобы разрешить колбэк.
Синтаксис ок, зачем нам тогда промисы?
Что если вы захотите сделать последующее асинхронное действие?
Давайте представим, что вместо простого сложения чисел единожды, нам надо будет сделать это 3 раза. В обычной функции, мы делаем это:
Как это выглядит с колбэками?
Этот синтаксис менее дружелюбен. Он выглядит как пирамида, но люди обычно называют подобное «колбэк адом», потому что колбэки, вложенные в колбэки, кхм, представьте, что у вас 10 колбэков и ваш код будет вложен 10 раз.
Побег из колбэк ада
Промисы приходят на помощь. Давайте посмотрим на тот же код, но по версии промисов.
Новичок на районе: Observables
Перед тем как закончить с промисами, есть кое-что, что пришло для того, чтобы облегчить работу с асинхронными данными — это Observables.
Observables — это ленивые потоки событий, которые могут выдать ноль или больше событий, а могут и вообще не закончиться.
Некоторые ключевые различия между промисами и observables:
Observable.fromPromise конвертит промис в observable поток
Observables могут делать много забавных вещей довольно легко. Для примера, delay добавляет функцию за 3 секунды с всего-лишь одной строкой кода или пробовать заново, так что вы можете делать запрос определенное количество раз.
Заключение
Узнайте про колбэки и промисы. Поймите их и используйте их. Не беспокойтесь о Observables, пока что. Все трое могут повлиять процесс разработки, но всё это будет зависеть от ситуации.
Email подписка!
Знакомство с промисами в JavaScript
Jan 15, 2020 · 4 min read
Если вы не совсем в курсе современных тенденций JavaScript, то, по крайней мере, слышали о промисах ранее, но не знаете, где и как их можно было бы применить.
Промисы служат для управления асинхронными операциями в JavaScript. Они достаточно легки в применении и могут избавить вас от многих неприятностей. Когда у вас имеется множество асинхронных операций, промисы могут избавить от лишней мороки с формированием огромного числа обратных вызовов, которые могут перерасти в кошмар и сделать код неуправляемым.
Проблема обратных вызовов
Хотя промисы и существую т уже на протяжении нескольких лет, давайте все же вспомним времена, когда их не было. Это поможет лучше понять, почему они являются хорошим способом управления асинхронными операциями.
Изначально для этого применялись функции обратного вызова. Они были наиболее удобными в этом отношении, но все равно имели ряд недостатков. Давайте взглянем на такой пример:
Как вы видите, функция loadScript передается параметром обратного вызова, который выполняется после загрузки скрипта. Все это работает прекрасно, но возникает один большой вопрос: “Что случится, если скрипт по какой-либо причине не загрузится?”. Со стороны разработчика будет наивным игнорировать такую возможность.
Как же перестраховаться? Достаточно просто. Мы добавляем еще один параметр в функцию обратного вызова, который сообщает об отсутствии ошибок при загрузке скрипта.
Но что, если у нас будет ситуация, в которой мы будем зависеть от загрузки не одного изображения, а, например, трех или более?
Именно такие ситуации и приводят к возникновению кошмара в обратных вызовах.
Краткое введение
Прежде чем приступить к рассмотрению промисов и принципов их работы, давайте взглянем, что на эту тему говорит MDN:
“Объект Promise представляет окончательное выполнение (или провал) асинхронной операции, а также ее итоговое значение.”
Как отмечено в MDN, промисы применяются для управления асинхронными операциями. Фактически же они позволяют не только легко работать с множественными операциями, но и более эффективно обрабатывать ошибки, что гораздо сложнее делать при помощи обратных вызовов и событий. В дополнение к этому они еще и повышают читаемость кода.
Промис может иметь одно из четырех состояний:
Промисы в коде
Теперь давайте создадим простой пример промиса для лучшего понимания:
В этом примере мы пробуем запросить данные из API. Если API вернет HTTP код состояния 200, значит запрос оказался успешным. В противном случае он провалился.
Как же нам применять промис, который мы только что создали?
All и Race
Идеальным примером применения Promise.all может выступить одновременная множественная отправка AJAX запросов.
Вместо ожидания завершения всех промисов, Promise.race запускается, как только оказывается выполнен или отклонен хотя бы один промис в цепочке.
Promise API
В классе Promise есть 5 статических методов. Давайте познакомимся с ними.
Promise.all
Допустим, нам нужно запустить множество промисов параллельно и дождаться, пока все они выполнятся.
Например, параллельно загрузить несколько файлов и обработать результат, когда он готов.
Метод Promise.all принимает массив промисов (может принимать любой перебираемый объект, но обычно используется массив) и возвращает новый промис.
Новый промис завершится, когда завершится весь переданный список промисов, и его результатом будет массив их результатов.
Обратите внимание, что порядок элементов массива в точности соответствует порядку исходных промисов. Даже если первый промис будет выполняться дольше всех, его результат всё равно будет первым в массиве.
Например, если у нас есть массив ссылок, то мы можем загрузить их вот так:
А вот пример побольше, с получением информации о пользователях GitHub по их логинам из массива (мы могли бы получать массив товаров по их идентификаторам, логика та же):
Если один промис завершается с ошибкой, то весь Promise.all завершается с ней, полностью забывая про остальные промисы в списке. Их результаты игнорируются.
Обычно, Promise.all(. ) принимает перебираемый объект промисов (чаще всего массив). Но если любой из этих объектов не является промисом, он передаётся в итоговый массив «как есть».
Например, здесь результат: [1, 2, 3]
Promise.allSettled
Promise.all завершается с ошибкой, если она возникает в любом из переданных промисов. Это подходит для ситуаций «всё или ничего», когда нам нужны все результаты для продолжения:
Метод Promise.allSettled всегда ждёт завершения всех промисов. В массиве результатов будет
Например, мы хотели бы загрузить информацию о множестве пользователей. Даже если в каком-то запросе ошибка, нас всё равно интересуют остальные.
Используем для этого Promise.allSettled :
Массив results в строке (*) будет таким:
То есть, для каждого промиса у нас есть его статус и значение/ошибка.
Полифил
Promise.race
Например, тут результат будет 1 :
Быстрее всех выполнился первый промис, он и дал результат. После этого остальные промисы игнорируются.
Promise.resolve/reject
Методы Promise.resolve и Promise.reject редко используются в современном коде, так как синтаксис async/await (мы рассмотрим его чуть позже) делает его, в общем-то, не нужным.
Этот метод используют для совместимости: когда ожидается, что функция возвратит именно промис.
Promise.reject
На практике этот метод почти никогда не используется.
Итого
Мы ознакомились с пятью статическими методами класса Promise :
Введение в промисы в JavaScript
Промис представляет собой объект, в который параметром передается функция, внутри которой нужно размещать наш асинхронный код:
Звучит запутано, поэтому давайте посмотрим на примере. Пусть у меня есть вот такой асинхронный код:
Для начала нужно обернуть наш асинхронный код в промис:
Этого, однако, не достаточно. Мы должны в явном виде указать, что наш асинхронный код завершился. В этом нам поможет специальная функция завершения, автоматически попадающая в первый параметр функции, если он указан:
С помощью функции завершения мы можем явно указать промису, что асинхронный код завершился. Для этого мы должны вызвать эту функцию в нужном нам месте:
При этом, если мы хотим передать вовне какой-то результат асинхронного кода, мы можем передать его параметром нашей функции завершения:
Можно, конечно же, избавится от промежуточной переменной:
Теперь в любом другом месте мы можем вызвать метод then нашего промиса:
Результат работы промиса попадет в первый параметр функции, если мы пожелаем его указать:
Сделайте промис, внутри которого будет задержка в 5 секунд, после которой промис должен выполнится, своим результатом вернув какой-нибудь текст. Выведите этот текст на экран.
Исключительные ситуации
Давайте теперь научимся обрабатывать исключительные ситуации, случившиеся внутри промиса. В случае возникновения такой ситуации мы должны отклонить промис с помощью специальной функции отклонения, которая автоматически попадает во второй параметр функции промиса:
Как правило, приведенный выше код записывают более компактно, вот так:
Использование объекта с ошибкой
Более принято в функцию reject передавать не строку с ошибкой, а объект с ошибкой:
Только обработка ошибок
В then можно указать только функцию-обработчик исключительной ситуации, передав вместо первого параметра null :
В таком случае удобно воспользоваться сокращенным синтаксисом через метод catch :
Состояния промиса
Промис может находится в одном из трех состояний. При создании промис находится в ожидании ( pending ), а затем может стать исполненным ( fulfilled ), вернув полученный результат, или отклоненным ( rejected ), вернув причину отказа. Вы можете посмотреть переход из одного состояния в другой, запустив следующий код:
Учтите, что состояния fulfilled и rejected неизменны: если промис перешел в одно из этих состояний, то он уже не сможет перейти в другое. Давайте посмотрим на примере. В следующем коде вызов reject случится раньше, поэтому вызов resolve будет проигнорирован: