Работа с сетью: технология AJAX
Основные методы асинхронных запросов
Для взаимодействия с сервером без перезагрузки страницы в JavaScript применяется AJAX. Наиболее современный и удобный подход - Fetch API. Он основан на промисах и предоставляет лаконичный синтаксис.
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Ошибка:', error));Ajax js (ajax в javascript)
Метод fetch принимает URL и возвращает промис. Первый вызов .then преобразует ответ в JSON, второй - обрабатывает данные. Блок .catch перехватывает сетевые ошибки.
Типичные проблемы:
- Ошибка CORS - сервер не разрешает запрос с другого домена. Решение: настройка сервера или использование прокси.
- Ответ не в формате JSON - вызов response.json() выбросит исключение. Следует проверять response.ok.
- Отсутствие обработки статусов HTTP - промис fetch не отклоняется при 404 или 500. Необходимо проверять response.status.
Как выполнить запрос с помощью XMLHttpRequest?
Классический способ, поддерживаемый всеми браузерами. Подходит для устаревших проектов или когда требуется детальный контроль над запросом.
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);
xhr.onload = function() {
if (xhr.status === 200) {
const data = JSON.parse(xhr.responseText);
console.log(data);
} else {
console.error('Ошибка:', xhr.status);
}
};
xhr.onerror = function() {
console.error('Сетевая ошибка');
};
xhr.send();new XMLHttpRequest() создаёт объект запроса. Параметр true в open означает асинхронный режим. Обработчики onload и onerror реагируют на результат.
Частые ошибки:
- Забытый вызов send() - запрос не отправится.
- Использование синхронного режима (false) блокирует интерфейс.
- Некорректный парсинг JSON через JSON.parse без проверки xhr.responseType.
Как использовать async/await с Fetch?
Синтаксис async/await делает асинхронный код похожим на синхронный, улучшая читаемость. Рекомендуется для сложных цепочек запросов.
async function loadData() {
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) throw new Error('HTTP ' + response.status);
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Ошибка:', error);
}
}
loadData();Ключевое слово await приостанавливает выполнение функции до завершения промиса. Блок try...catch обрабатывает исключения.
Возможные сложности:
- Забытая обработка response.ok - при 404 промис не отклоняется, но данные не получены.
- Использование await вне асинхронной функции - вызовет синтаксическую ошибку.
Как отправить POST запрос с JSON?
Для отправки данных на сервер используется POST. Fetch позволяет передать объект с опциями: method, headers, body.
const payload = { name: 'Анна', age: 30 };
fetch('https://api.example.com/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
})
.then(response => response.json())
.then(data => console.log('Успех:', data))
.catch(error => console.error('Ошибка:', error));Заголовок Content-Type указывает серверу формат данных. JSON.stringify преобразует объект в строку.
Проблемы:
- Неправильный заголовок - сервер может не распознать данные.
- Отсутствие JSON.stringify - запрос отправит [object Object].
- Несоответствие типа данных в ответе сервера.
Как обрабатывать ошибки и таймауты?
Таймаут для fetch не встроен, но реализуется через AbortController. Ошибки сети обрабатываются в .catch или try...catch.
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);
fetch('https://api.example.com/data', { signal: controller.signal })
.then(response => {
clearTimeout(timeoutId);
if (!response.ok) throw new Error('HTTP ' + response.status);
return response.json();
})
.then(data => console.log(data))
.catch(error => {
if (error.name === 'AbortError') {
console.error('Запрос прерван по таймауту');
} else {
console.error('Ошибка:', error);
}
});AbortController создаёт сигнал, который передаётся в fetch. Вызов abort() отклоняет промис с исключением AbortError.
Сложности:
- Необходимость очищать таймер после успешного ответа, чтобы избежать лишнего abort.
- Поддержка AbortController - все современные браузеры, для старых требуется полифилл.
Расширенные примеры использования AJAX
1. Отправка формы с использованием FormData
const form = document.querySelector('form');
form.addEventListener('submit', async (event) => {
event.preventDefault();
const formData = new FormData(form);
try {
const response = await fetch('/submit', { method: 'POST', body: formData });
const result = await response.text();
console.log('Результат:', result);
} catch (error) {
console.error('Ошибка отправки формы:', error);
}
});// Вывод при успехе: 'Форма отправлена' или 'Спасибо'
FormData автоматически собирает поля формы. Content-Type устанавливается как multipart/form-data.
2. Загрузка файла с индикацией прогресса (XMLHttpRequest)
const xhr = new XMLHttpRequest();
xhr.open('POST', '/upload', true);
xhr.upload.onprogress = (event) => {
if (event.lengthComputable) {
const percent = (event.loaded / event.total) * 100;
console.log(`Загружено ${percent}%`);
}
};
xhr.onload = () => console.log('Файл загружен');
const fileInput = document.querySelector('input[type="file"]');
const file = fileInput.files[0];
const formData = new FormData();
formData.append('file', file);
xhr.send(formData);// Загружено 45% // Загружено 100% // Файл загружен
Событие progress на объекте upload позволяет отслеживать ход отправки. Fetch не поддерживает прогресс отправки напрямую.
3. Параллельные запросы с Promise.all
const urls = [
'https://api.example.com/users',
'https://api.example.com/posts'
];
Promise.all(urls.map(url =>
fetch(url).then(res => {
if (!res.ok) throw new Error('Ошибка ' + res.status);
return res.json();
})
))
.then(([users, posts]) => {
console.log('Пользователи:', users);
console.log('Посты:', posts);
})
.catch(error => console.error(error));// Пользователи: [{id:1, name:'Иван'}, ...]
// Посты: [{id:10, title:'Пример'}, ...]Promise.all ожидает завершения всех промисов. При отклонении любого из них срабатывает catch.
4. Запрос с кастомными заголовками и авторизацией
const token = 'my-secret-token';
fetch('https://api.example.com/secure', {
headers: {
'Authorization': `Bearer ${token}`,
'X-Custom-Header': 'custom-value'
}
})
.then(response => response.json())
.then(data => console.log(data));// Данные защищённого ресурса
Кастомные заголовки передаются в объекте headers. Обратите внимание на CORS - сервер должен разрешать эти заголовки.
5. Отмена запроса при помощи AbortController (полный пример)
const controller = new AbortController();
const signal = controller.signal;
// Эмуляция медленного запроса
const fetchPromise = fetch('https://httpbin.org/delay/5', { signal });
// Отмена через 2 секунды
setTimeout(() => controller.abort(), 2000);
fetchPromise
.then(response => response.json())
.then(data => console.log(data))
.catch(err => {
if (err.name === 'AbortError') {
console.log('Запрос отменён по таймауту');
} else {
console.error('Ошибка:', err);
}
});// Запрос отменён по таймауту
AbortController применим не только к fetch, но и к другим асинхронным операциям.
6. Обработка JSONP (для старых кросс-доменных запросов)
function jsonp(url, callbackName) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = `${url}?callback=${callbackName}`;
window[callbackName] = (data) => {
resolve(data);
document.body.removeChild(script);
delete window[callbackName];
};
script.onerror = () => reject(new Error('JSONP error'));
document.body.appendChild(script);
});
}
jsonp('https://api.example.com/data', 'myCallback')
.then(data => console.log(data))
.catch(error => console.error(error));// { result: 'jsonp data' }JSONP использует тег script для обхода CORS. Сервер должен поддерживать callback. Современные API чаще используют CORS, поэтому JSONP считается устаревшим.
7. Использование async iterators для потоковой обработки (fetch + ReadableStream)
async function* streamResponse(url) {
const response = await fetch(url);
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
yield decoder.decode(value, { stream: true });
}
}
(async () => {
for await (let chunk of streamResponse('https://api.example.com/stream')) {
console.log('Получен фрагмент:', chunk);
}
})();// Получен фрагмент: '{"id":1}'
// Получен фрагмент: ',{"id":2}'
// ...Потоковое чтение полезно для больших ответов или событийных каналов (SSE).