Promise
Промисы в JavaScript — это объекты, представляющие собой завершение или неудачу асинхронной операции и её результат. Они помогают управлять асинхронным кодом, позволяя прикреплять обработчики к асинхронным действиям, несколько упрощая код, который бы иначе использовал сложные конструкции с callback-функциями.
Как промисы улучшают асинхронный код
- Цепочки промисов. Промисы позволяют создавать цепочки асинхронных операций, где результат одной операции передаётся в следующую. Это упрощает структуру кода по сравнению с вложенными колбэками, которые часто приводят к “callback hell”.
- Обработка ошибок. Промисы предоставляют удобный механизм обработки ошибок с помощью метода
.catch()
, который позволяет перехватывать ошибки на любом этапе выполнения цепочки промисов. - Параллельное выполнение. С помощью
Promise.all
можно эффективно обрабатывать параллельное выполнение нескольких асинхронных операций, что значительно ускоряет выполнение кода, когда операции не зависят друг от друга.
Как можно использовать промисы
- API запросы. Для обработки HTTP-запросов к серверу. Это позволяет управлять загрузкой данных и последующими действиями с помощью цепочек
.then()
и.catch()
для обработки ответов и ошибок. - В более сложных приложениях можно использовать промисы для координации нескольких процессов, например для проверки состояния формы регистрации – проверки уникальности имени пользователя и email через Promise.all проверять 2 промиса одновременно.
- file API, доступ к камере и микрофону, работа вебворкера, indexedDB – подробнее в статье ниже.
Promis и Event Loop на примере fetch
fetch
является API браузера для осуществления сетевых запросов, и он может использоваться для “параллельной” отправки нескольких запросов. (Это особенно актуально в современных реализациях HTTP/2, где браузер может устанавливать одно соединение к серверу и передавать множество запросов через это соединение, используя мультиплексирование).
- Когда делается асинхронный запрос с помощью
fetch
, и сервер возвращает ответ, сам сетевой ответ возвращается как макротаска. - Изменение состояния промиса. Как только ответ доступен (т.е. макротаска выполнена), промис, который возвращается методом
fetch
, переходит в состояние “разрешён” (resolved), если запрос успешен, или в состояние “отклонён” (rejected), если произошла ошибка (например, сетевая ошибка или ошибка HTTP-статуса). - После того, как промис разрешается или отклоняется, все обработчики, прикреплённые к этому промису через
.then
,.catch
или.finally
, помещаются в очередь микротасок (и будут обработаны немедленно после текущей макротаски и перед любыми следующими макротасками).
Пример 1. Promise с .then, .catch
const fetchUser = () => {
return new Promise((res, rej) => {
setTimeout(() => res('done'), 2000);
})
}
fetchUser()
.then((data) => {
console.log(data);
})
.catch(err => console.error(err));
Пример 2. Тотже Promise, но с async await
const fetchUser = () => {
return new Promise((res, rej) => {
setTimeout(() => res('done'), 2000);
})
}
const getUser = async () => {
try {
const data = await fetchUser();
console.log(data);
} catch (e) {
console.error(e)
}
}
- Оба метода одинаково эффективны и выбор между ними часто зависит от предпочтений разработчика и конкретного контекста использования.
.then()
и.catch()
могут быть более наглядными в ситуациях с множественными последовательными асинхронными операциями, которые нужно чётко разделить.async/await
сtry/catch
предпочтительнее, когда нужно написать код, который легче читать и поддерживать, особенно когда асинхронные операции тесно связаны или когда в коде много условий и циклов.
Как еще можно применять промисы
Промисы часто используются для управления операциями, результат которых неизвестен и которые могут занять неопределенное время. Хотя сетевые запросы являются одним из самых частых примеров использования промисов, существует множество других сценариев, особенно на фронтенде, где промисы могут быть полезны.
Доступ к медиа-устройствам
Операции, связанные с использованием медиа-устройств, таких как камера или микрофон, также являются асинхронными. Например, запрос разрешения на использование камеры и получение потока данных:
navigator.mediaDevices.getUserMedia({ video: true })
.then(stream => {
// Видео поток доступен
document.querySelector('video').srcObject = stream;
})
.catch(error => {
// Обработка ошибки, если пользователь не дал разрешения
console.error("Unable to access the camera", error);
});
Операции с IndexedDB
IndexedDB — это асинхронное API для клиентского хранения значительных объемов структурированных данных. Промисы используются для управления транзакциями и запросами к базе данных.
const dbPromise = idb.open('keyval-store', 1, upgradeDB => {
upgradeDB.createObjectStore('keyval');
});
dbPromise.then(db => {
const tx = db.transaction('keyval', 'readwrite');
tx.objectStore('keyval').put('world', 'hello');
return tx.complete;
}).then(() => {
console.log('Added hello world into keyval');
});
Чтение файлов с использованием File API
File API позволяет асинхронно читать содержимое файлов, выбранных пользователем или доступных в системе.
const fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', event => {
const file = event.target.files[0];
const reader = new FileReader();
reader.readAsText(file);
reader.onload = () => console.log(reader.result);
reader.onerror = () => console.error("Failed to read file", reader.error);
});
Web Workers
Web Workers позволяют выполнять сложные или длительные вычисления в фоновом потоке, не блокируя главный поток. Обмен сообщениями между основным потоком и воркером может быть организован с использованием промисов.
const worker = new Worker('worker.js');
worker.postMessage('Do work');
new Promise((resolve, reject) => {
worker.onmessage = e => resolve(e.data);
worker.onerror = e => reject(e);
}).then(result => {
console.log('Received from worker:', result);
}).catch(error => {
console.error('Worker error:', error);
});
Анимации и транзиции
Промисы могут быть использованы для управления последовательностями анимаций или переходов, особенно когда их нужно синхронизировать с другими асинхронными действиями.
animateElement(element)
.then(() => loadAdditionalData())
.then(data => displayData(data))
.catch(err => console.error("Animation or data loading failed", err));
function animateElement(element) {
return new Promise(resolve => {
element.classList.add('animate');
element.addEventListener('transitionend', resolve, { once: true });
});
}