Ты разрабатываешь интернет-магазин. При добавлении товара в корзину нужно сообщить серверу. При открытии страницы — загрузить каталог. Раньше для этого использовали XMLHttpRequest — громоздкий и неудобный. Fetch API — современная замена: лаконичный, промис-based, встроен в браузер и Node.js 18+.
const response = await fetch('https://api.shop.ru/products')
console.log(response.status) // 200
console.log(response.ok) // true (если status 200-299)
const products = await response.json() // два await: один для запроса, один для тела
console.log(products[0].name)Самая коварная особенность Fetch — HTTP-ошибки не бросают исключений:
// Fetch не выбросит ошибку даже при 404 или 500:
const response = await fetch('/api/nonexistent')
console.log(response.status) // 404
console.log(response.ok) // false — но исключения нет!
// Нужно проверять самостоятельно:
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}Fetch бросает исключение только при сетевых проблемах: нет интернета, DNS не отвечает.
const response = await fetch('/api/orders', {
method: 'POST',
headers: {
'Content-Type': 'application/json', // обязательно!
'Authorization': 'Bearer ' + token,
},
body: JSON.stringify({
productId: 42,
quantity: 2,
}),
})
if (!response.ok) throw new Error('Ошибка создания заказа')
const order = await response.json()
console.log('Заказ создан, id:', order.id)В реальных проектах Fetch оборачивают в утилиту, чтобы не повторять проверки:
async function apiRequest(url, options = {}) {
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
...options.headers,
},
...options,
})
if (!response.ok) {
const body = await response.json().catch(() => null)
const message = body?.message ?? `HTTP ${response.status}`
throw new Error(message)
}
if (response.status === 204) return null // No Content
return response.json()
}
// Чистое использование — без ручных проверок
const products = await apiRequest('/api/products')
const order = await apiRequest('/api/orders', {
method: 'POST',
body: JSON.stringify({ productId: 1, qty: 2 }),
})// При быстрой печати в поиске — отменяем предыдущий запрос
let controller = null
async function search(query) {
controller?.abort()
controller = new AbortController()
try {
const response = await fetch(`/api/search?q=${query}`, {
signal: controller.signal,
})
return response.json()
} catch (e) {
if (e.name === 'AbortError') return null // запрос отменён — не ошибка
throw e
}
}1. Не проверили response.ok — молча обрабатывают ошибку как успех:
// Сломано:
const response = await fetch('/api/users/999')
const user = await response.json() // { message: 'Not found' }
console.log(user.name) // undefined — откуда ошибка?
// Исправлено:
const response = await fetch('/api/users/999')
if (!response.ok) throw new Error('Пользователь не найден')
const user = await response.json()2. Забыли второй await у response.json():
// Сломано:
const response = await fetch('/api/products')
const data = response.json() // нет await — data это Promise!
console.log(data.length) // undefined
// Исправлено:
const data = await response.json()3. Нет Content-Type для POST — сервер не понимает тело:
// Сломано:
await fetch('/api/orders', {
method: 'POST',
body: JSON.stringify(order),
})
// Исправлено:
await fetch('/api/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(order),
}){ next: { revalidate: 60 } })Универсальный API-клиент для интернет-магазина с GET/POST
// Мок fetch для sandbox (в браузере — нативный fetch с реальными URL)
const db = {
products: [
{ id: 1, name: 'MacBook Pro', price: 189990, stock: 5 },
{ id: 2, name: 'iPhone 15', price: 89990, stock: 12 },
],
orders: [],
}
async function fetch(url, options = {}) {
await new Promise(r => setTimeout(r, 40))
const method = (options.method ?? 'GET').toUpperCase()
const path = url.replace('https://api.shop.ru', '')
if (path === '/products' && method === 'GET')
return { ok: true, status: 200, json: async () => db.products }
if (path.startsWith('/products/') && method === 'GET') {
const id = parseInt(path.split('/')[2])
const product = db.products.find(p => p.id === id)
if (!product)
return { ok: false, status: 404, json: async () => ({ message: 'Товар не найден' }) }
return { ok: true, status: 200, json: async () => product }
}
if (path === '/orders' && method === 'POST') {
const body = JSON.parse(options.body)
const order = { id: db.orders.length + 1, ...body, status: 'pending' }
db.orders.push(order)
return { ok: true, status: 201, json: async () => order }
}
return { ok: false, status: 404, json: async () => ({ message: 'Маршрут не найден' }) }
}
// Универсальная обёртка
async function apiRequest(url, options = {}) {
const response = await fetch(url, {
headers: { 'Content-Type': 'application/json', ...options.headers },
...options,
})
if (!response.ok) {
const body = await response.json().catch(() => null)
throw new Error(body?.message ?? `HTTP ${response.status}`)
}
return response.json()
}
const ShopAPI = {
getProducts: () => apiRequest('https://api.shop.ru/products'),
getProduct: (id) => apiRequest(`https://api.shop.ru/products/${id}`),
createOrder: (data) => apiRequest('https://api.shop.ru/orders', {
method: 'POST',
body: JSON.stringify(data),
}),
}
async function main() {
const products = await ShopAPI.getProducts()
console.log('Каталог:')
products.forEach(p => console.log(` ${p.name} — ${p.price.toLocaleString('ru-RU')} ₽`))
const laptop = await ShopAPI.getProduct(1)
console.log(`\nЗагружен: ${laptop.name}, в наличии: ${laptop.stock} шт.`)
const order = await ShopAPI.createOrder({ productId: 1, quantity: 1 })
console.log(`\nЗаказ #${order.id} создан, статус: ${order.status}`)
try {
await ShopAPI.getProduct(999)
} catch (e) {
console.log(`\nОшибка: ${e.message}`)
}
}
main()Ты разрабатываешь интернет-магазин. При добавлении товара в корзину нужно сообщить серверу. При открытии страницы — загрузить каталог. Раньше для этого использовали XMLHttpRequest — громоздкий и неудобный. Fetch API — современная замена: лаконичный, промис-based, встроен в браузер и Node.js 18+.
const response = await fetch('https://api.shop.ru/products')
console.log(response.status) // 200
console.log(response.ok) // true (если status 200-299)
const products = await response.json() // два await: один для запроса, один для тела
console.log(products[0].name)Самая коварная особенность Fetch — HTTP-ошибки не бросают исключений:
// Fetch не выбросит ошибку даже при 404 или 500:
const response = await fetch('/api/nonexistent')
console.log(response.status) // 404
console.log(response.ok) // false — но исключения нет!
// Нужно проверять самостоятельно:
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}Fetch бросает исключение только при сетевых проблемах: нет интернета, DNS не отвечает.
const response = await fetch('/api/orders', {
method: 'POST',
headers: {
'Content-Type': 'application/json', // обязательно!
'Authorization': 'Bearer ' + token,
},
body: JSON.stringify({
productId: 42,
quantity: 2,
}),
})
if (!response.ok) throw new Error('Ошибка создания заказа')
const order = await response.json()
console.log('Заказ создан, id:', order.id)В реальных проектах Fetch оборачивают в утилиту, чтобы не повторять проверки:
async function apiRequest(url, options = {}) {
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
...options.headers,
},
...options,
})
if (!response.ok) {
const body = await response.json().catch(() => null)
const message = body?.message ?? `HTTP ${response.status}`
throw new Error(message)
}
if (response.status === 204) return null // No Content
return response.json()
}
// Чистое использование — без ручных проверок
const products = await apiRequest('/api/products')
const order = await apiRequest('/api/orders', {
method: 'POST',
body: JSON.stringify({ productId: 1, qty: 2 }),
})// При быстрой печати в поиске — отменяем предыдущий запрос
let controller = null
async function search(query) {
controller?.abort()
controller = new AbortController()
try {
const response = await fetch(`/api/search?q=${query}`, {
signal: controller.signal,
})
return response.json()
} catch (e) {
if (e.name === 'AbortError') return null // запрос отменён — не ошибка
throw e
}
}1. Не проверили response.ok — молча обрабатывают ошибку как успех:
// Сломано:
const response = await fetch('/api/users/999')
const user = await response.json() // { message: 'Not found' }
console.log(user.name) // undefined — откуда ошибка?
// Исправлено:
const response = await fetch('/api/users/999')
if (!response.ok) throw new Error('Пользователь не найден')
const user = await response.json()2. Забыли второй await у response.json():
// Сломано:
const response = await fetch('/api/products')
const data = response.json() // нет await — data это Promise!
console.log(data.length) // undefined
// Исправлено:
const data = await response.json()3. Нет Content-Type для POST — сервер не понимает тело:
// Сломано:
await fetch('/api/orders', {
method: 'POST',
body: JSON.stringify(order),
})
// Исправлено:
await fetch('/api/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(order),
}){ next: { revalidate: 60 } })Универсальный API-клиент для интернет-магазина с GET/POST
// Мок fetch для sandbox (в браузере — нативный fetch с реальными URL)
const db = {
products: [
{ id: 1, name: 'MacBook Pro', price: 189990, stock: 5 },
{ id: 2, name: 'iPhone 15', price: 89990, stock: 12 },
],
orders: [],
}
async function fetch(url, options = {}) {
await new Promise(r => setTimeout(r, 40))
const method = (options.method ?? 'GET').toUpperCase()
const path = url.replace('https://api.shop.ru', '')
if (path === '/products' && method === 'GET')
return { ok: true, status: 200, json: async () => db.products }
if (path.startsWith('/products/') && method === 'GET') {
const id = parseInt(path.split('/')[2])
const product = db.products.find(p => p.id === id)
if (!product)
return { ok: false, status: 404, json: async () => ({ message: 'Товар не найден' }) }
return { ok: true, status: 200, json: async () => product }
}
if (path === '/orders' && method === 'POST') {
const body = JSON.parse(options.body)
const order = { id: db.orders.length + 1, ...body, status: 'pending' }
db.orders.push(order)
return { ok: true, status: 201, json: async () => order }
}
return { ok: false, status: 404, json: async () => ({ message: 'Маршрут не найден' }) }
}
// Универсальная обёртка
async function apiRequest(url, options = {}) {
const response = await fetch(url, {
headers: { 'Content-Type': 'application/json', ...options.headers },
...options,
})
if (!response.ok) {
const body = await response.json().catch(() => null)
throw new Error(body?.message ?? `HTTP ${response.status}`)
}
return response.json()
}
const ShopAPI = {
getProducts: () => apiRequest('https://api.shop.ru/products'),
getProduct: (id) => apiRequest(`https://api.shop.ru/products/${id}`),
createOrder: (data) => apiRequest('https://api.shop.ru/orders', {
method: 'POST',
body: JSON.stringify(data),
}),
}
async function main() {
const products = await ShopAPI.getProducts()
console.log('Каталог:')
products.forEach(p => console.log(` ${p.name} — ${p.price.toLocaleString('ru-RU')} ₽`))
const laptop = await ShopAPI.getProduct(1)
console.log(`\nЗагружен: ${laptop.name}, в наличии: ${laptop.stock} шт.`)
const order = await ShopAPI.createOrder({ productId: 1, quantity: 1 })
console.log(`\nЗаказ #${order.id} создан, статус: ${order.status}`)
try {
await ShopAPI.getProduct(999)
} catch (e) {
console.log(`\nОшибка: ${e.message}`)
}
}
main()Ты разрабатываешь клиент для API задачника (To-do list). Используй мок-функцию `fetch` (уже написана). Реализуй: 1. `getTasks()` — GET /tasks, возвращает массив задач 2. `createTask(title)` — POST /tasks с телом `{ title }`, возвращает созданную задачу 3. `deleteTask(id)` — DELETE /tasks/:id, бросает ошибку если не найдена Каждая функция должна проверять `response.ok` и бросать ошибку при неуспехе.
getTasks: throw new Error("Ошибка загрузки задач"). createTask body: JSON.stringify({ title }). deleteTask: const err = await response.json(); throw new Error(err.message). Не забудь Content-Type для POST.