Node.jsМодуль 1: Введение в Node.js
Асинхронность в Node.js
Callbacks, Promises и async/await
Цель урока
В этом уроке ты научишься:
- Работать с callbacks
- Использовать Promises
- Применять async/await
- Выполнять параллельные операции
Callbacks
Традиционный подход в Node.js — Error-First Callbacks:
const fs = require('fs');
// Первый аргумент — ошибка, второй — результат
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) {
console.error('Ошибка:', err.message);
return;
}
console.log('Данные:', data);
});Callback Hell
// Проблема — вложенные callbacks
fs.readFile('file1.txt', 'utf8', (err, data1) => {
if (err) return console.error(err);
fs.readFile('file2.txt', 'utf8', (err, data2) => {
if (err) return console.error(err);
fs.readFile('file3.txt', 'utf8', (err, data3) => {
if (err) return console.error(err);
console.log(data1, data2, data3);
});
});
});Promises
Промисификация
const fs = require('fs');
const util = require('util');
// Преобразование callback в Promise
const readFile = util.promisify(fs.readFile);
readFile('file.txt', 'utf8')
.then(data => console.log(data))
.catch(err => console.error(err));fs/promises
const fs = require('fs/promises');
// Встроенные промисы
fs.readFile('file.txt', 'utf8')
.then(data => console.log(data))
.catch(err => console.error(err));Цепочки
const fs = require('fs/promises');
fs.readFile('file1.txt', 'utf8')
.then(data1 => {
console.log('Файл 1:', data1);
return fs.readFile('file2.txt', 'utf8');
})
.then(data2 => {
console.log('Файл 2:', data2);
return fs.readFile('file3.txt', 'utf8');
})
.then(data3 => {
console.log('Файл 3:', data3);
})
.catch(err => console.error('Ошибка:', err));async/await
const fs = require('fs/promises');
async function readFiles() {
try {
const data1 = await fs.readFile('file1.txt', 'utf8');
console.log('Файл 1:', data1);
const data2 = await fs.readFile('file2.txt', 'utf8');
console.log('Файл 2:', data2);
const data3 = await fs.readFile('file3.txt', 'utf8');
console.log('Файл 3:', data3);
} catch (err) {
console.error('Ошибка:', err.message);
}
}
readFiles();Параллельное выполнение
Promise.all
const fs = require('fs/promises');
async function readAllFiles() {
try {
const [data1, data2, data3] = await Promise.all([
fs.readFile('file1.txt', 'utf8'),
fs.readFile('file2.txt', 'utf8'),
fs.readFile('file3.txt', 'utf8')
]);
console.log(data1, data2, data3);
} catch (err) {
console.error('Одна из операций завершилась ошибкой');
}
}Promise.allSettled
const results = await Promise.allSettled([
fs.readFile('exists.txt', 'utf8'),
fs.readFile('not-exists.txt', 'utf8'),
fs.readFile('another.txt', 'utf8')
]);
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Файл ${index}: ${result.value}`);
} else {
console.log(`Файл ${index}: Ошибка - ${result.reason.message}`);
}
});Promise.race
// Первый завершившийся
const result = await Promise.race([
fetch('https://api1.example.com'),
fetch('https://api2.example.com')
]);Обработка ошибок
try/catch
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Ошибка загрузки:', error.message);
throw error; // Пробрасываем дальше
}
}Глобальная обработка
// Необработанные промисы
process.on('unhandledRejection', (reason, promise) => {
console.error('Необработанный промис:', reason);
});
// Необработанные исключения
process.on('uncaughtException', (error) => {
console.error('Необработанное исключение:', error);
process.exit(1);
});Паттерны
Retry
async function fetchWithRetry(url, retries = 3, delay = 1000) {
for (let i = 0; i < retries; i++) {
try {
return await fetch(url);
} catch (error) {
if (i === retries - 1) throw error;
console.log(`Попытка ${i + 1} не удалась, повтор через ${delay}ms`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}Timeout
function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Timeout')), ms);
});
return Promise.race([promise, timeout]);
}
// Использование
try {
const data = await withTimeout(fetch(url), 5000);
} catch (error) {
if (error.message === 'Timeout') {
console.log('Превышено время ожидания');
}
}Throttle
async function processWithThrottle(items, fn, concurrency = 5) {
const results = [];
for (let i = 0; i < items.length; i += concurrency) {
const batch = items.slice(i, i + concurrency);
const batchResults = await Promise.all(batch.map(fn));
results.push(...batchResults);
}
return results;
}
// Использование
const urls = ['url1', 'url2', /* ... много URL */];
const data = await processWithThrottle(urls, fetch, 5);Практика
Задание 1: Последовательное чтение
Задача: Прочитай 3 файла последовательно.
Решение:
const fs = require('fs/promises');
async function readSequentially(files) {
const results = [];
for (const file of files) {
const data = await fs.readFile(file, 'utf8');
results.push(data);
}
return results;
}
readSequentially(['a.txt', 'b.txt', 'c.txt'])
.then(console.log)
.catch(console.error);Задание 2: Параллельное чтение
Задача: Прочитай 3 файла параллельно.
Решение:
const fs = require('fs/promises');
async function readParallel(files) {
return Promise.all(files.map(file => fs.readFile(file, 'utf8')));
}
readParallel(['a.txt', 'b.txt', 'c.txt'])
.then(console.log)
.catch(console.error);Задание 3: Retry
Задача: Реализуй функцию с повторными попытками.
Решение:
async function retry(fn, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
return await fn();
} catch (error) {
if (i === retries - 1) throw error;
console.log(`Попытка ${i + 1} не удалась`);
}
}
}
// Использование
await retry(() => fetch('https://api.example.com'), 3);Проверь себя
- Что такое Error-First Callback?
- Чем
Promise.allотличается отPromise.allSettled? - Как обработать таймаут промиса?