Js почему вс код подчеркивает необработанный промис
Перейти к содержимому

Js почему вс код подчеркивает необработанный промис

Промисы: обработка ошибок

Цепочки промисов отлично подходят для перехвата ошибок. Если промис завершается с ошибкой, то управление переходит в ближайший обработчик ошибок. На практике это очень удобно.

Например, в представленном ниже примере для fetch указана неправильная ссылка (сайт не существует), и .catch перехватывает ошибку:

Как видно, .catch не обязательно должен быть сразу после ошибки, он может быть далее, после одного или даже нескольких .then

Или, может быть, с сервером всё в порядке, но в ответе мы получим некорректный JSON. Самый лёгкий путь перехватить все ошибки – это добавить .catch в конец цепочки:

Если все в порядке, то такой .catch вообще не выполнится. Но если любой из промисов будет отклонён (проблемы с сетью или некорректная json-строка, или что угодно другое), то ошибка будет перехвачена.

Неявный try…catch

Вокруг функции промиса и обработчиков находится "невидимый try..catch ". Если происходит исключение, то оно перехватывается, и промис считается отклонённым с этой ошибкой.

Например, этот код:

…Работает так же, как и этот:

"Невидимый try..catch " вокруг промиса автоматически перехватывает ошибку и превращает её в отклонённый промис.

Это работает не только в функции промиса, но и в обработчиках. Если мы бросим ошибку ( throw ) из обработчика ( .then ), то промис будет считаться отклонённым, и управление перейдёт к ближайшему обработчику ошибок.

Это происходит для всех ошибок, не только для тех, которые вызваны оператором throw . Например, программная ошибка:

Финальный .catch перехватывает как промисы, в которых вызван reject , так и случайные ошибки в обработчиках.

Пробрасывание ошибок

Как мы уже заметили, .catch ведёт себя как try..catch . Мы можем иметь столько обработчиков .then , сколько мы хотим, и затем использовать один .catch в конце, чтобы перехватить ошибки из всех обработчиков.

В обычном try..catch мы можем проанализировать ошибку и повторно пробросить дальше, если не можем её обработать. То же самое возможно для промисов.

Если мы пробросим ( throw ) ошибку внутри блока .catch , то управление перейдёт к следующему ближайшему обработчику ошибок. А если мы обработаем ошибку и завершим работу обработчика нормально, то продолжит работу ближайший успешный обработчик .then .

В примере ниже .catch успешно обрабатывает ошибку:

Здесь блок .catch завершается нормально. Поэтому вызывается следующий успешный обработчик .then .

В примере ниже мы видим другую ситуацию с блоком .catch . Обработчик (*) перехватывает ошибку и не может обработать её (например, он знает как обработать только URIError ), поэтому ошибка пробрасывается далее:

Управление переходит от первого блока .catch (*) к следующему (**) , вниз по цепочке.

Необработанные ошибки

Что произойдёт, если ошибка не будет обработана? Например, мы просто забыли добавить .catch в конец цепочки, как здесь:

В случае ошибки выполнение должно перейти к ближайшему обработчику ошибок. Но в примере выше нет никакого обработчика. Поэтому ошибка как бы «застревает», её некому обработать.

На практике, как и при обычных необработанных ошибках в коде, это означает, что что-то пошло сильно не так.

Что происходит, когда обычная ошибка не перехвачена try..catch ? Скрипт умирает с сообщением в консоли. Похожее происходит и в случае необработанной ошибки промиса.

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

В браузере мы можем поймать такие ошибки, используя событие unhandledrejection :

Это событие является частью стандарта HTML.

Если происходит ошибка, и отсутствует её обработчик, то генерируется событие unhandledrejection , и соответствующий объект event содержит информацию об ошибке.

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

Что именно возвращает promise?

Изучаю тему промисов, и второй день, несмотря на все усилия, не могу добиться ясности в вопросе, что именно возвращает промис, и как с этим работать?

Пример1: функция для того, чтобы отправлять что-то на условный сервер. Кладем в переменную функцию для получения промиса.

Как я понимаю, внутри функции в res должен записываться объект Promise, (ведь его возвращает нам функция fetch). Но console.log(res) выводит в консоль объект Response.

Вопрос 1: откуда берется Response, если fetch должен возвращать объект Promise ?

Хорошо, Response так Response. Наверное при вызове функции я должен его и получить(раз она его возвращает). Сразу после присвоения функции смотрю, что она возвращает:

И она возвращает как раз объект Promise в состоянии pending.

Вопрос 2: внутри функции мы видели что в переменной res лежит объект Response, мы возвращаем его как результат функции. Почему возвращается Promise?

Вопрос 3: почему этот Promise в состоянии pending? Это значит что он в этот момент выполняется? как и когда тогда получать результаты? И что он в итоге вернет?

Дальше. VSCode и курс, который я прохожу, говорят что вместо

потому что тогда слово await будет иметь смысл. Это понятно. Но.

Вопрос 4: если res у нас все таки Response, и к нему применимы методы, то что содержится в этом Response, если я просто отправляю данные на сервер? Кроме информации о статусе запроса, там что? Те самые данные что я отправил?

окей, я применю метод:

Вопрос 5: я ведь получу очередной промис, а не данные в формате JSON. Для получения самих данных мне придется писать для этого промиса then? Как их вообще грамотно получать?

Пример2: функция для того, чтобы отправлять что-то на условный сервер.

Моя логика такая: при вызове

я получу результат работы функции res.json(), то есть промис. По моей логике, я могу получить из него данные, отдельно задав этому промису then, который задаст инструкцию в случае если полученные данные успешно получилось превратить в json:

Но в jsonData у меня оказывается undefined. Почему? и как мне получить объект JSON сразу из функции?

И последнее. Выполняю код:

Как я понимаю, ключевые слова async и await указывают коду, что 1 — функция асинхронная 2 — сначала мы начнем выполнять функцию, в которой выведем в консоль res, потом получим результат работы fetch, потом уже применим на нем метод json(), затем запишем его в переменную и вернем 3 — следовательно уже после этого возвращенное значение присвоится переменной data 4 — и уже после этого мы можем вывести ее в консоль

Использование промисов

Promise (промис) — это объект, представляющий результат успешного или неудачного завершения асинхронной операции. Так как большинство людей пользуются уже созданными промисами, это руководство начнём с объяснения использования вернувшихся промисов до объяснения принципов создания.

В сущности, промис — это возвращаемый объект, в который вы записываете два колбэка вместо того, чтобы передать их функции.

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

…современные функции возвращают промис, в который вы записываете ваши колбэки:

Мы называем это асинхронным вызовом функции. У этого соглашения есть несколько преимуществ. Давайте рассмотрим их.

Гарантии

В отличие от старомодных переданных колбэков промис даёт некоторые гарантии:

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

Но наиболее непосредственная польза от промисов — цепочка вызовов (chaining).

Цепочка вызовов

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

Вот в чём магия: функция then возвращает новый промис, отличающийся от первоначального:

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

По сути, каждый вызванный промис означает успешное завершение предыдущих шагов в цепочке.

Раньше выполнение нескольких асинхронных операций друг за другом приводило к классической «Вавилонской башне» колбэков:

В современных функциях мы записываем колбэки в возвращаемые промисы — формируем цепочку промисов:

Аргументы then необязательны, а catch(failureCallback) — это сокращение для then(null, failureCallback) . Вот как это выражено с помощью стрелочных функций:

Важно: Всегда возвращайте промисы в return, иначе колбэки не будут сцеплены и ошибки могут быть не пойманы (стрелочные функции неявно возвращают результат, если скобки <> вокруг тела функции опущены).

Цепочка вызовов после catch

Можно продолжить цепочку вызовов после ошибки, т. е. после catch , что полезно для выполнения новых действий даже после того, как действие вернёт ошибку в цепочке вызовов. Ниже приведён пример:

В результате выведется данный текст:

Заметьте, что текст «Выведи это» не вывелся, потому что «Где-то произошла ошибка» привела к отказу

Распространение ошибки

Вы могли ранее заметить, что failureCallback повторяется три раза в «pyramid of doom», а в цепочке промисов всего лишь один раз:

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

Эта симметрия с синхронным кодом лучше всего показывает себя в синтаксическом сахаре async / await в ECMAScript 2017:

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

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

Создание промиса вокруг старого колбэка

Promise может быть создан с помощью конструктора. Это может понадобится только для старых API.

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

Смешивание старого колбэк-стиля и промисов проблематично. В случае неудачного завершения saySomething или программной ошибки, нельзя обработать ошибку.

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

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

Композиция

Promise.resolve() и Promise.reject() короткий способ создать уже успешные или отклонённые промисы соответственно. Это иногда бывает полезно.

Promise.all() и Promise.race() — два метода запустить асинхронные операции параллельно.

Последовательное выполнение композиции возможно при помощи хитрости JavaScript:

Фактически, мы превращаем массив асинхронных функций в цепочку промисов равносильно: Promise.resolve().then(func1).then(func2);

Это также можно сделать, объединив композицию в функцию, в функциональном стиле программирования:

composeAsync функция примет любое количество функций в качестве аргументов и вернёт новую функцию которая примет в параметрах начальное значение, переданное по цепочке. Это удобно, потому что некоторые или все функции могут быть либо асинхронными, либо синхронными, и они гарантированно выполнятся в правильной последовательности:

В ECMAScript 2017, последовательные композиции могут быть выполнены более простым способом с помощью async/await:

Порядок выполнения

Чтобы избежать сюрпризов, функции, переданные в then никогда не будут вызваны синхронно, даже с уже разрешённым промисом:

Вместо немедленного выполнения, переданная функция встанет в очередь микрозадач, а значит выполнится, когда очередь будет пустой в конце текущего вызова JavaScript цикла событий (event loop), т.е. очень скоро:

Вложенность

Простые цепочки promise лучше оставлять без вложений, так как вложенность может быть результатом небрежной структуры. Смотрите распространённые ошибки.

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

Обратите внимание, что необязательный шаги здесь выделены отступом.

Внутренний оператор catch нейтрализует и перехватывает ошибки только от doSomethingOptional() и doSomethingExtraNice(), после чего код возобновляется с помощью moreCriticalStuff(). Важно, что в случае сбоя doSomethingCritical() его ошибка перехватывается только последним (внешним) catch.

Частые ошибки

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

Первая ошибка это неправильно сцепить вещи между собой. Такое происходит когда мы создаём промис но забываем вернуть его. Как следствие, цепочка сломана, но правильнее было бы сказать что теперь у нас есть две независимые цепочки, соревнующиеся за право разрешится первой. Это означает, что doFourthThing() не будет ждать doSomethingElse() или doThirdThing() пока тот закончится, и будет исполнятся параллельно с ними, это, вероятно, не то что хотел разработчик. Отдельные цепочки также имеют отдельную обработку ошибок, что приводит к необработанным ошибкам.

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

Третья ошибка это забыть закончить цепочку ключевым словом catch . Незаконченные цепочки приводят к необработанным отторжениям промисов в большинстве браузеров.

Хорошим примером является всегда либо возвращать либо заканчивать цепочки промисов, и как только вы получаете новый промис, возвращайте его сразу же, чтобы не усложнять код излишней вложенностью:

Обратите внимание что () => x это сокращённая форма () => < return x; >.

Теперь у нас имеется единственная определённая цепочка с правильной обработкой ошибок.

Использование async / await предотвращает большинство, если не все вышеуказанные ошибки, но взамен появляется другая частая ошибка — забыть ключевое слово await .

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *