В коде на JavaScript для одностраничного приложения используется setTimeout для ожидания завершения AJAX‑запроса: покажите типичную ошибку синхронизации, объясните, почему она возникает, и опишите, как правильно организовать асинхронную логику с промисами или async/await
Типичная ошибка - Разработчик ставит фиксированную задержку через setTimeout и надеется, что AJAX‑запрос успеет завершиться, например: запрос идёт, затем через 100010001000 мс читают результат — иногда работает, иногда нет. Пример неправильного кода ```javascript let data; fetch('/api/data') .then(r => r.json()) .then(d => { data = d; }); // Надеемся, что через 1000 мс data уже заполнен setTimeout(() => { console.log(data.length); // может быть undefined или бросить ошибку }, 1000); ``` Почему это ошибка - Время отклика сервера и сеть непредсказуемы: ответ может прийти быстрее или медленнее заданной задержки. - Такой код создаёт гонку (race condition): чтение происходит независимо от фактического завершения асинхронной операции. - setTimeout не "ждёт" завершения запроса — он просто откладывает выполнение на фиксированное время. Как правильно: промисы (.then) или async/await 1) С промисами (.then) - Привязывайте последующие действия к разрешению промиса, а не к таймеру. ```javascript fetch('/api/data') .then(response => { if (!response.ok) throw new Error(response.statusText); return response.json(); }) .then(data => { console.log(data.length); // выполняется только после получения данных }) .catch(err => { console.error('Ошибка запроса:', err); }); ``` 2) С async/await (более декларативно) ```javascript async function loadData() { try { const response = await fetch('/api/data'); if (!response.ok) throw new Error(response.statusText); const data = await response.json(); console.log(data.length); } catch (err) { console.error('Ошибка запроса:', err); } } loadData(); ``` Дополнительно (полезные практики) - Если нужно одновременно ждать нескольких запросов — используйте \`Promise.all\`: ```javascript const [a, b] = await Promise.all([fetch('/a').then(r => r.json()), fetch('/b').then(r => r.json())]); ``` - Для отмены запроса используйте AbortController: ```javascript const ac = new AbortController(); fetch('/api/data', { signal: ac.signal }).then(...).catch(err => { if (err.name === 'AbortError') console.log('Запрос отменён'); }); ac.abort(); // отмена ``` Кратко: не заменяйте синхронизацию таймером. Привязывайте код к завершению промиса (.then / await), обрабатывайте ошибки и используйте AbortController или Promise.all при необходимости.
- Разработчик ставит фиксированную задержку через setTimeout и надеется, что AJAX‑запрос успеет завершиться, например: запрос идёт, затем через 100010001000 мс читают результат — иногда работает, иногда нет.
Пример неправильного кода
```javascript
let data;
fetch('/api/data')
.then(r => r.json())
.then(d => { data = d; });
// Надеемся, что через 1000 мс data уже заполнен
setTimeout(() => {
console.log(data.length); // может быть undefined или бросить ошибку
}, 1000);
```
Почему это ошибка
- Время отклика сервера и сеть непредсказуемы: ответ может прийти быстрее или медленнее заданной задержки.
- Такой код создаёт гонку (race condition): чтение происходит независимо от фактического завершения асинхронной операции.
- setTimeout не "ждёт" завершения запроса — он просто откладывает выполнение на фиксированное время.
Как правильно: промисы (.then) или async/await
1) С промисами (.then)
- Привязывайте последующие действия к разрешению промиса, а не к таймеру.
```javascript
fetch('/api/data')
.then(response => {
if (!response.ok) throw new Error(response.statusText);
return response.json();
})
.then(data => {
console.log(data.length); // выполняется только после получения данных
})
.catch(err => {
console.error('Ошибка запроса:', err);
});
```
2) С async/await (более декларативно)
```javascript
async function loadData() {
try {
const response = await fetch('/api/data');
if (!response.ok) throw new Error(response.statusText);
const data = await response.json();
console.log(data.length);
} catch (err) {
console.error('Ошибка запроса:', err);
}
}
loadData();
```
Дополнительно (полезные практики)
- Если нужно одновременно ждать нескольких запросов — используйте \`Promise.all\`:
```javascript
const [a, b] = await Promise.all([fetch('/a').then(r => r.json()), fetch('/b').then(r => r.json())]);
```
- Для отмены запроса используйте AbortController:
```javascript
const ac = new AbortController();
fetch('/api/data', { signal: ac.signal }).then(...).catch(err => {
if (err.name === 'AbortError') console.log('Запрос отменён');
});
ac.abort(); // отмена
```
Кратко: не заменяйте синхронизацию таймером. Привязывайте код к завершению промиса (.then / await), обрабатывайте ошибки и используйте AbortController или Promise.all при необходимости.