Современные подходы к асинхронной загрузке данных на JavaScript
Основные подходы к асинхронным запросам
Как эффективно загружать данные с сервера без перезагрузки страницы?
Современный стандарт предлагает использовать Fetch API в сочетании с синтаксисом async/await. Это читаемый и гибкий способ обмена данными.
async function loadUsers() {\n try {\n const response = await fetch('https://jsonplaceholder.typicode.com/users');\n if (!response.ok) throw new Error('Ошибка HTTP: ' + response.status);\n const users = await response.json();\n users.forEach(user => {\n const li = document.createElement('li');\n li.textContent = user.name;\n document.getElementById('userList').appendChild(li);\n });\n } catch (error) {\n console.error('Не удалось загрузить пользователей:', error);\n }\n}\nloadUsers();Javascript веб (веб-разработка на javascript)
Типичная ошибка: забыть проверить response.ok. В результате при ошибке сервера (404, 500) код продолжает работать с некорректными данными. Также важно обрабатывать исключения с try...catch.
Цель использования: получение JSON, текста, Blob или другого ресурса с последующим обновлением DOM. Подходит для большинства AJAX-запросов.
Как выполнить асинхронный запрос без современных API?
Классический XMLHttpRequest всё ещё работает во всех браузерах. Он использует события и callback-функции.
const xhr = new XMLHttpRequest();\nxhr.open('GET', 'https://jsonplaceholder.typicode.com/posts/1', true);\nxhr.onload = function() {\n if (xhr.status >= 200 && xhr.status < 300) {\n const post = JSON.parse(xhr.responseText);\n document.getElementById('postTitle').textContent = post.title;\n } else {\n console.error('Ошибка загрузки:', xhr.status);\n }\n};\nxhr.onerror = function() {\n console.error('Сетевая ошибка');\n};\nxhr.send();шаблон html css js (создание сайта на html/css/js)
Проблемы: более громоздкий код, необходимость вручную парсить JSON, обработка разных состояний (onload, onerror, ontimeout). Возможна утечка памяти при отмене запроса.
Цель использования: поддержка старых браузеров или проекты, где Fetch недоступен.
Как обработать асинхронный запрос с помощью цепочки промисов?
Fetch возвращает промис, поэтому можно не использовать async/await, а соединять .then() и .catch().
fetch('https://jsonplaceholder.typicode.com/comments?postId=1')\n .then(response => {\n if (!response.ok) throw new Error('HTTP ' + response.status);\n return response.json();\n })\n .then(comments => {\n comments.slice(0, 3).forEach(comment => {\n const p = document.createElement('p');\n p.textContent = comment.body;\n document.getElementById('comments').appendChild(p);\n });\n })\n .catch(error => console.error(error));Ошибка: забыть вернуть промис внутри .then() – следующий .then() получит undefined.
Цель: когда не хочется использовать async/await, например в простых скриптах.
Как упростить HTTP-запросы с помощью библиотеки?
Библиотека Axios предоставляет лаконичный интерфейс и автоматическую обработку JSON.
axios.get('https://jsonplaceholder.typicode.com/todos/1')\n .then(response => {\n document.getElementById('todo').textContent = response.data.title;\n })\n .catch(error => {\n console.error('Ошибка Axios:', error.response?.status);\n });Недостаток: нужно подключать внешнюю зависимость. Возможны конфликты версий.
Цель: проекты с интенсивными запросами, где нужны интерцепторы, отмена запросов, автоматическая трансформация данных.
Как организовать двустороннюю связь в реальном времени?
WebSocket устанавливает постоянное соединение с сервером, позволяя обмениваться данными мгновенно.
const socket = new WebSocket('ws://example.com/chat');\nsocket.onopen = () => {\n socket.send(JSON.stringify({type: 'join', room: 'general'}));\n};\nsocket.onmessage = (event) => {\n const msg = JSON.parse(event.data);\n const div = document.createElement('div');\n div.textContent = msg.user + ': ' + msg.text;\n document.getElementById('chat').appendChild(div);\n};\nsocket.onerror = (err) => console.error('WebSocket error', err);Проблемы: WebSocket не работает через обычные прокси, требует поддержки сервера. При обрыве соединения нужно реализовать реконнект.
Цель: чаты, игры, котировки акций, совместное редактирование.
Расширенные примеры кода
Ниже приведены более сложные сценарии использования асинхронных запросов.
1. Отправка формы с файлом через FormData и Fetch
const form = document.getElementById('uploadForm');\nform.addEventListener('submit', async (e) => {\n e.preventDefault();\n const formData = new FormData(form);\n try {\n const response = await fetch('/upload', {\n method: 'POST',\n body: formData\n });\n if (response.ok) {\n const result = await response.json();\n console.log('Файл загружен:', result.url);\n } else {\n console.error('Ошибка загрузки', response.status);\n }\n } catch (err) {\n console.error('Сетевая ошибка', err);\n }\n});(В консоль выводится URL загруженного файла)
Пояснение: FormData автоматически кодирует поля и файлы в multipart/form-data. Не нужно устанавливать заголовок Content-Type, браузер сделает это сам.
2. Запрос с таймаутом и отменой через AbortController
function fetchWithTimeout(url, timeout = 5000) {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeout);\n return fetch(url, { signal: controller.signal })\n .finally(() => clearTimeout(timer));\n}\n\nfetchWithTimeout('https://jsonplaceholder.typicode.com/users', 3000)\n .then(response => response.json())\n .then(data => console.log(data))\n .catch(err => {\n if (err.name === 'AbortError') {\n console.error('Запрос отменён по таймауту');\n } else {\n console.error('Ошибка:', err);\n }\n });(При превышении таймаута в консоли 'Запрос отменён по таймауту')
Пояснение: AbortController позволяет отменить один или несколько fetch-запросов. Полезно при уходе пользователя со страницы.
3. Прогресс загрузки файла через XMLHttpRequest
const xhr = new XMLHttpRequest();\nxhr.open('POST', '/upload', true);\nconst formData = new FormData();\nformData.append('file', document.getElementById('fileInput').files[0]);\n\nxhr.upload.onprogress = (event) => {\n if (event.lengthComputable) {\n const percent = Math.round((event.loaded / event.total) * 100);\n document.getElementById('progressBar').value = percent;\n document.getElementById('progressText').textContent = percent + '%';\n }\n};\nxhr.onload = () => {\n if (xhr.status === 200) {\n console.log('Загрузка завершена');\n }\n};\nxhr.send(formData);(Анимируется прогресс бар и отображается процент)
Пояснение: XMLHttpRequest предоставляет событие upload.onprogress для отслеживания отправки. Fetch не поддерживает отслеживание прогресса отправки (только получения через ReadableStream).
4. Повторные попытки при ошибках (retry)
async function fetchWithRetry(url, options = {}, retries = 3, delay = 1000) {\n for (let i = 0; i < retries; i++) {\n try {\n const response = await fetch(url, options);\n if (!response.ok) throw new Error('HTTP ' + response.status);\n return await response.json();\n } catch (err) {\n if (i === retries - 1) throw err;\n console.warn('Попытка ' + (i+1) + ' не удалась, повтор через ' + delay + 'ms');\n await new Promise(resolve => setTimeout(resolve, delay));\n delay *= 2; // экспоненциальная задержка\n }\n }\n}\n\nfetchWithRetry('https://jsonplaceholder.typicode.com/posts/1', {}, 3, 500)\n .then(data => console.log('Успех:', data))\n .catch(err => console.error('Ошибка после всех попыток:', err));(В случае неудач выводятся сообщения о попытках; при успехе - данные)
Пояснение: Полезно при нестабильном соединении. Важно не перегружать сервер, поэтому используется экспоненциальная задержка.
5. Параллельная загрузка нескольких ресурсов с Promise.all
async function loadMultiple() {\n const urls = [\n 'https://jsonplaceholder.typicode.com/posts',\n 'https://jsonplaceholder.typicode.com/users',\n 'https://jsonplaceholder.typicode.com/comments'\n ];\n try {\n const responses = await Promise.all(urls.map(url => fetch(url)));\n const data = await Promise.all(responses.map(res => res.json()));\n console.log('Посты:', data[0].length);\n console.log('Пользователи:', data[1].length);\n console.log('Комментарии:', data[2].length);\n } catch (error) {\n console.error('Один из запросов упал:', error);\n }\n}\nloadMultiple();(В консоли отображаются длины массивов)
Пояснение: Promise.all ждёт все промисы. Если хотя бы один отклоняется, весь Promise.all отклоняется. Для частичной устойчивости используйте Promise.allSettled.