Написано Розарио Де Кьяра✏️
В JavaScript обещания — это мощный инструмент для обработки асинхронных операций, который особенно полезен в событиях, связанных с пользовательским интерфейсом. Они представляют собой значение, которое может быть доступно не сразу, но будет решено в какой-то момент в будущем.
Обещания позволяют (или должны позволять) разработчикам писать более чистый и управляемый код при работе с такими задачами, как вызовы API, взаимодействие с пользователем или анимация. Используя такие методы, как .then(), .catch() и .finally(), Promises обеспечивают более интуитивный способ обработки сценариев успеха и ошибок, избегая пресловутого «ада обратных вызовов».
В этой статье мы будем использовать новый (март 2024 г.) метод Promise.withResolvers(), который позволяет писать более чистый и простой код, возвращая объект, содержащий три вещи: новое обещание и две функции, одна для разрешения обещания. а другой — отклонить его. Поскольку это недавнее обновление, для выполнения примеров из этой статьи вам понадобится последняя версия среды выполнения Node (v>22).
В двух следующих функционально эквивалентных фрагментах кода мы можем сравнить старый подход и новый подход назначения метода для разрешения или отклонения обещания:
let resolve, reject; const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); Math.random() > 0.5 ? resolve("ok") : reject("not ok");
В приведенном выше коде вы можете увидеть наиболее традиционное использование промиса: вы создаете экземпляр нового объекта промиса, а затем в конструкторе вам нужно назначить две функции: разрешить и отклонить, которые будут вызываться при нужный.
В следующем фрагменте кода тот же фрагмент кода был переписан с использованием нового метода Promise.withResolvers(), и он выглядит проще:
const { promise, resolve, reject } = Promise.withResolvers(); Math.random() > 0.5 ? resolve("ok") : reject("not ok");
Здесь вы можете увидеть, как работает новый подход. Он возвращает обещание, для которого вы можете вызвать метод .then() и две функции: разрешить и отклонить.
Традиционный подход к промисам инкапсулирует логику создания и обработки событий в одной функции, что может быть ограничено, если для разрешения или отклонения обещания требуется несколько условий или разные части кода.
Напротив, Promise.withResolvers() обеспечивает большую гибкость, отделяя создание обещания от логики разрешения, что делает его пригодным для управления сложными условиями или несколькими событиями. Однако в простых случаях использования традиционный метод может оказаться проще и привычнее для тех, кто привык к стандартным шаблонам обещаний.
Теперь мы можем протестировать новый подход на более реалистичном примере. В приведенном ниже коде вы можете увидеть простой пример вызова API:
function fetchData(url) { return new Promise((resolve, reject) => { fetch(url) .then(response => { // Check if the response is okay (status 200-299) if (response.ok) { return response.json(); // Parse JSON if response is okay } else { // Reject the promise if the response is not okay reject(new Error('API Invocation failed')); } }) .then(data => { // Resolve the promise with the data resolve(data); }) .catch(error => { // Catch and reject the promise if there is a network error reject(error); }); }); } // Example usage const apiURL = ''; fetchData(apiURL) .then(data => { // Handle the resolved data console.log('Data received:', data); }) .catch(error => { // Handle any errors that occurred console.error('Error occurred:', error); });
Функция fetchData предназначена для приема URL-адреса и возврата обещания, которое обрабатывает вызов API с использованием API-интерфейса fetch. Он обрабатывает ответ, проверяя, находится ли статус ответа в диапазоне 200–299, что указывает на успех.
В случае успеха ответ анализируется как JSON, и обещание разрешается с использованием полученных данных. Если ответ не удался, обещание отклоняется с соответствующим сообщением об ошибке. Кроме того, функция включает обработку ошибок для обнаружения любых сетевых ошибок и отклонения обещания в случае возникновения такой ошибки.
В примере показано, как использовать эту функцию, показывая, как управлять обработанными данными с помощью блока .then() и обрабатывать ошибки с помощью блока .catch(), обеспечивая соответствующее управление как успешным получением данных, так и ошибками.
В приведенном ниже коде мы переписываем функцию fetchData(), используя новый метод Promise.withResolvers():
function fetchData(url) { const { promise, resolve, reject } = Promise.withResolvers(); fetch(url) .then(response => { // Check if the response is okay (status 200-299) if (response.ok) { return response.json(); // Parse JSON if response is okay } else { // Reject the promise if the response is not okay reject(new Error('API Invocation failed')); } }) .then(data => { // Resolve the promise with the data resolve(data); }) .catch(error => { // Catch and reject the promise if there is a network error reject(error); }); return promise; }
Как видите, приведенный выше код более читабелен, а роль объекта Promise ясна: функция fetchData вернет Promise, который будет успешно решен или завершится неудачей, вызывая – в каждом случае – правильный метод . Вы можете найти приведенный выше код в репозитории с именем api.invocacy.{old|new}.js.
В следующем коде показано, как реализовать метод отмены обещания. Как вы, возможно, знаете, вы не можете отменить обещание в JavaScript. Промисы представляют собой результат асинхронной операции и предназначены для разрешения или отклонения после создания, без встроенного механизма для их отмены.
Это ограничение возникает из-за того, что промисы имеют определенный процесс перехода состояний; они начинаются как ожидающие решения и после урегулирования не могут изменить состояние. Они предназначены для инкапсуляции результата операции, а не для управления самой операцией, что означает, что они не могут влиять или отменять основной процесс. Такой выбор дизайна делает Promises простым и ориентированным на представление конечного результата операции:
const cancellablePromise = () => { const { promise, resolve, reject } = Promise.withResolvers(); promise.cancel = () => { reject("the promise got cancelled"); }; return promise; };
В приведенном выше коде вы можете увидеть объект с именем cancellablePromise, который представляет собой обещание с дополнительным методом cancel(), который, как вы можете видеть, просто вызывает вызов метода отклонения. Это всего лишь синтаксический сахар, который не отменяет обещание JavaScript, хотя может помочь в написании более понятного кода.
Альтернативный подход — использовать AbortController и AbortSignal, которые можно привязать к базовой операции (например, HTTP-запросу) для ее отмены при необходимости. Из документации вы можете видеть, что подход AbortController и AbortSignal представляет собой более выразительную реализацию того, что мы реализовали в приведенном выше коде: как только AbortSignal вызывается, обещание просто отклоняется.
Другой подход — использовать библиотеки реактивного программирования, такие как RxJS, которые предлагают реализацию шаблона Observable, более сложный контроль над потоками асинхронных данных, включая возможности отмены.
Если говорить о практических вариантах использования, обещания хорошо подходят для обработки отдельных асинхронных операций, таких как получение данных из API. Напротив, Observables идеально подходят для управления потоками данных, такими как пользовательский ввод, события WebSocket или ответы HTTP, где с течением времени может быть выдано несколько значений.
Мы уже пояснили, что после запуска Promises нельзя отменить, тогда как Observables можно отменить, отписавшись от потока. Общая идея заключается в том, что с Observables у вас есть явная структура возможного взаимодействия с объектом:
Это показано в приведенном ниже коде:
import { Observable } from 'rxjs'; const observable = new Observable(subscriber => { subscriber.next(1); subscriber.next(2); subscriber.next(3); subscriber.complete(); }); const observer = observable.subscribe({ next(x) { console.log('Received value:', x); }, complete() { console.log('Observable completed'); } }); observer.unsubscribe();
Этот код нельзя переписать с помощью промисов, поскольку Observable возвращает три значения, а промис можно выполнить только один раз.
Для дальнейших экспериментов с методом отмены подписки мы можем добавить еще один наблюдатель, который будет использовать метод takeWhile(): он позволит наблюдателю ждать, пока значения будут соответствовать определенному условию; например, в приведенном ниже коде он продолжает получать события от Observable, пока значение не равно 2:
import { Observable, takeWhile } from 'rxjs'; const observable = new Observable(subscriber => { subscriber.next(1); subscriber.next(2); subscriber.next(3); subscriber.complete(); }); const observer1 = observable.subscribe({ next(x) { console.log('Received by 1 value:', x); }, complete() { console.log('Observable 1 completed'); } }); const observer2 = observable.pipe( takeWhile(value => value != "2") ).subscribe(value => console.log('Received by 2 value:', value));
В приведенном выше коде Observer1 аналогичен тому, что мы уже видели: он просто подпишется и продолжит получать все события от Observable. Второй, Observer2, будет получать элементы из Observable, пока условие выполняется. В данном случае это означает, что значение отличается от 2.
Из выполнения вы можете увидеть, как работают два разных механизма:
$ node observable.mjs Received by 1 value: 1 Received by 1 value: 2 Received by 1 value: 3 Observable 1 completed Received by 2 value: 1 $
В этой статье мы исследовали новый механизм выделения промиса в JavaScript и изложили некоторые возможные способы отмены промиса до его завершения. Мы также сравнили промисы с объектами Observable, которые не только предлагают функции промисов, но и расширяют их, позволяя множественную отправку событий и правильный механизм отказа от подписки.
Отладка кода — всегда утомительная задача. Но чем лучше вы понимаете свои ошибки, тем легче их исправить.
LogRocket позволяет вам понять эти ошибки новыми и уникальными способами. Наше решение для мониторинга внешнего интерфейса отслеживает взаимодействие пользователя с вашими интерфейсами JavaScript, чтобы дать вам возможность увидеть, какие именно действия пользователя привели к ошибке.
LogRocket записывает журналы консоли, время загрузки страниц, трассировку стека, медленные сетевые запросы/ответы с телами заголовков, метаданные браузера и пользовательские журналы. Понять влияние вашего кода JavaScript никогда не будет проще!
Попробуйте бесплатно.
Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.
Copyright© 2022 湘ICP备2022001581号-3