Promise

Промисы в JavaScript — это объекты, представляющие собой завершение или неудачу асинхронной операции и её результат. Они помогают управлять асинхронным кодом, позволяя прикреплять обработчики к асинхронным действиям, несколько упрощая код, который бы иначе использовал сложные конструкции с callback-функциями.

Как промисы улучшают асинхронный код

  1. Цепочки промисов. Промисы позволяют создавать цепочки асинхронных операций, где результат одной операции передаётся в следующую. Это упрощает структуру кода по сравнению с вложенными колбэками, которые часто приводят к “callback hell”.
  2. Обработка ошибок. Промисы предоставляют удобный механизм обработки ошибок с помощью метода .catch(), который позволяет перехватывать ошибки на любом этапе выполнения цепочки промисов.
  3. Параллельное выполнение. С помощью Promise.all можно эффективно обрабатывать параллельное выполнение нескольких асинхронных операций, что значительно ускоряет выполнение кода, когда операции не зависят друг от друга.

Как можно использовать промисы

  1. API запросы. Для обработки HTTP-запросов к серверу. Это позволяет управлять загрузкой данных и последующими действиями с помощью цепочек .then() и .catch() для обработки ответов и ошибок.
  2. В более сложных приложениях можно использовать промисы для координации нескольких процессов, например для проверки состояния формы регистрации – проверки уникальности имени пользователя и email через Promise.all проверять 2 промиса одновременно.
  3. file API, доступ к камере и микрофону, работа вебворкера, indexedDB – подробнее в статье ниже.

Promis и Event Loop на примере fetch

fetch является API браузера для осуществления сетевых запросов, и он может использоваться для “параллельной” отправки нескольких запросов. (Это особенно актуально в современных реализациях HTTP/2, где браузер может устанавливать одно соединение к серверу и передавать множество запросов через это соединение, используя мультиплексирование).

  1. Когда делается асинхронный запрос с помощью fetch, и сервер возвращает ответ, сам сетевой ответ возвращается как макротаска.
  2. Изменение состояния промиса. Как только ответ доступен (т.е. макротаска выполнена), промис, который возвращается методом fetch, переходит в состояние “разрешён” (resolved), если запрос успешен, или в состояние “отклонён” (rejected), если произошла ошибка (например, сетевая ошибка или ошибка HTTP-статуса).
  3. После того, как промис разрешается или отклоняется, все обработчики, прикреплённые к этому промису через .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 });
  });
}

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

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