Как использовал генератор?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Использование генераторов в практике
Генератор — это функция в JavaScript, которая может приостанавливать и возобновлять своё выполнение. Она использует ключевое слово yield для возврата значений поочередно.
Синтаксис и базовое понимание
// Обычная функция
function regularFunc() {
return 1;
return 2; // Никогда не выполнится
}
// Генератор
function* generator() {
yield 1;
yield 2;
yield 3;
}
const gen = generator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }
Использование в реальных проектах
1. Обработка больших файлов (chunk по chunk)
Приходилось работать с CSV файлом размером 500MB. Загрузить в память весь файл — это неэффективно. Генератор позволил обрабатывать по 1000 строк за раз:
const fs = require('fs');
const readline = require('readline');
function* readLargeFile(filePath) {
const fileStream = fs.createReadStream(filePath);
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity
});
for await (const line of rl) {
yield line;
}
}
// Использование
const csvGenerator = readLargeFile('users.csv');
let processedCount = 0;
for (const line of csvGenerator) {
const user = parseCSV(line);
saveToDatabase(user);
processedCount++;
// Логируем прогресс
if (processedCount % 1000 === 0) {
console.log(`Обработано ${processedCount} пользователей`);
}
}
2. Асинхронная обработка с задержками (rate limiting)
Нужно было обработать API запросы с ограничением 10 запросов в секунду:
function* rateLimiter(items, requestsPerSecond) {
const delayMs = 1000 / requestsPerSecond;
let lastTime = Date.now();
for (const item of items) {
const now = Date.now();
const timeSinceLastRequest = now - lastTime;
if (timeSinceLastRequest < delayMs) {
yield new Promise(resolve =>
setTimeout(resolve, delayMs - timeSinceLastRequest)
);
}
yield fetch(`/api/users/${item.id}`);
lastTime = Date.now();
}
}
// Использование
(async () => {
const items = [{ id: 1 }, { id: 2 }, { id: 3 }];
for await (const request of rateLimiter(items, 10)) {
const response = await request;
console.log(response.status);
}
})();
3. Обход сложных структур данных (tree traversal)
Нужно было обойти древовидную структуру папок и файлов:
function* walkDir(dir) {
const files = fs.readdirSync(dir, { withFileTypes: true });
for (const file of files) {
const fullPath = path.join(dir, file.name);
if (file.isDirectory()) {
// Рекурсивный обход
yield* walkDir(fullPath);
} else {
yield fullPath;
}
}
}
// Использование
for (const filePath of walkDir('/path/to/dir')) {
console.log(`Найден файл: ${filePath}`);
if (filePath.endsWith('.log')) {
deleteFile(filePath);
}
}
4. Paginated API запросы (infinite scroll)
Работал с API, который возвращает данные paginated. Нужно было автоматически запрашивать следующую страницу:
function* fetchPaginatedData(apiUrl) {
let page = 1;
let hasMore = true;
while (hasMore) {
const response = await fetch(`${apiUrl}?page=${page}`);
const data = await response.json();
if (data.items.length === 0) {
hasMore = false;
return;
}
for (const item of data.items) {
yield item;
}
page++;
}
}
// Использование
for await (const item of fetchPaginatedData('/api/products')) {
console.log(item.name);
if (itemCount++ === 100) break; // Читаем первые 100 товаров
}
5. Создание простого event emitter'а
Использовал генератор для имитации event stream'а:
function* eventStream(userId) {
// Слушаем события в БД
while (true) {
const event = await db.query(
'SELECT * FROM events WHERE user_id = @id AND processed = false LIMIT 1',
{ id: userId }
);
if (event) {
yield event;
await db.query('UPDATE events SET processed = true WHERE id = @id', { id: event.id });
} else {
// Ждём 100ms перед следующей проверкой
await new Promise(resolve => setTimeout(resolve, 100));
}
}
}
// Использование
const stream = eventStream(userId);
for await (const event of stream) {
handleEvent(event);
}
Генераторы vs Асинхронные функции
До ES2017 (генераторы):
// Асинхронный код через генератор + промисы
function* fetchUserData(userId) {
const user = yield fetch(`/api/users/${userId}`).then(r => r.json());
const posts = yield fetch(`/api/users/${userId}/posts`).then(r => r.json());
return { user, posts };
}
// Нужен "коррунтин" для управления
function runGenerator(gen) {
const iterator = gen();
function handle(result) {
if (result.done) return Promise.resolve(result.value);
return Promise.resolve(result.value)
.then(res => handle(iterator.next(res)))
.catch(err => iterator.throw(err));
}
return handle(iterator.next());
}
runGenerator(() => fetchUserData(1));
После ES2017 (async/await):
// Это буквально сахар над генераторами, но удобнее
async function fetchUserData(userId) {
const user = await fetch(`/api/users/${userId}`).then(r => r.json());
const posts = await fetch(`/api/users/${userId}/posts`).then(r => r.json());
return { user, posts };
}
await fetchUserData(1);
Вывод: Для асинхронного кода теперь используем async/await, но генераторы остаются полезны для:
- Итерирования больших данных
- Lazy evaluation
- Реализации паттернов (как выше)
Когда НЕ использовать генераторы
// ✗ Неправильно: сложнее, чем обычный цикл
function* simpleLoop() {
for (let i = 0; i < 10; i++) {
yield i;
}
}
// ✓ Правильно: просто
for (let i = 0; i < 10; i++) {
console.log(i);
}
Практический вывод
Генераторы — это инструмент для lazy evaluation и больших данных. Они позволяют:
- Обрабатывать данные по частям (не загружая всё в память)
- Приостанавливать выполнение и возобновлять его
- Реализовывать custom iteration протоколы
Для асинхронного кода лучше использовать async/await.