← Назад к вопросам
Первую задачу будешь делать простую или сложную
2.0 Middle🔥 141 комментариев
#Soft Skills и рабочие процессы
Комментарии (1)
🐱
deepseek-v3.2PrepBro AI4 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Задача про Функции, Очереди и Стек
Я выбираю сложную задачу, так как она позволяет продемонстрировать глубокое понимание JavaScript, принципов функционального программирования и структур данных. Решим классическую задачу на реализацию функции с очередью вызовов и поддержкой стека (LIFO).
Постановка задачи
Реализовать функцию createQueueStackFn, которая:
- Принимает другую функцию
fnв качестве аргумента - Возвращает новую функцию, которая при вызове добавляет аргументы в очередь
- Очередь обрабатывается последовательно, но с поддержкой стека последних вызовов (последний добавленный вызов имеет приоритет)
- Функция
fnвыполняется с аргументами из очереди только когда предыдущий вызов завершен
Решение с подробным объяснением
/**
* Создает функцию с очередью вызовов и стековой приоритизацией
* @param {Function} fn - Исходная асинхронная функция
* @returns {Function} Функция с очередью и стеком
*/
function createQueueStackFn(fn) {
// Очередь вызовов
let queue = [];
// Флаг выполнения текущего вызова
let isProcessing = false;
// Вспомогательная функция для обработки очереди
const processQueue = async () => {
// Если уже выполняется или очередь пуста - выходим
if (isProcessing || queue.length === 0) return;
isProcessing = true;
// Берем последний элемент (стековая логика - LIFO)
// Но сохраняем предыдущие для возможного использования
const lastCallIndex = queue.length - 1;
const call = queue[lastCallIndex];
try {
// Выполняем функцию с аргументами
await fn(...call.args);
// Удаляем все вызовы до текущего (включительно)
// так как последний вызов "перекрывает" предыдущие
queue = queue.slice(lastCallIndex + 1);
} catch (error) {
// Обрабатываем ошибку, но продолжаем обработку очереди
console.error('Error in queued function:', error);
// Удаляем только обработанный вызов
queue.splice(lastCallIndex, 1);
} finally {
isProcessing = false;
// Если в очереди еще есть вызовы, обрабатываем следующий
if (queue.length > 0) {
// Используем setTimeout для избежания переполнения стека
setTimeout(processQueue, 0);
}
}
};
// Возвращаем новую функцию
return function(...args) {
// Добавляем вызов в очередь
queue.push({
args,
timestamp: Date.now()
});
// Запускаем обработку очереди, если не обрабатывается
if (!isProcessing) {
// Используем microtask для асинхронного запуска
Promise.resolve().then(processQueue);
}
// Можно вернуть Promise для отслеживания результата
return new Promise((resolve, reject) => {
// В реальной реализации нужно привязать resolve/reject к конкретному вызову
// Для упрощения опускаем эту логику
});
};
}
Ключевые особенности реализации
1. Очередь с стековой логикой
- Все вызовы добавляются в массив
queue - При обработке всегда берется последний элемент (LIFO - Last In First Out)
- После выполнения последнего вызова, все предыдущие удаляются как нерелевантные
2. Асинхронная обработка
- Используется
async/awaitдля работы с асинхронными функциями Promise.resolve().then()обеспечивает запуск в микротаскеsetTimeoutпредотвращает переполнение стека вызовов при рекурсии
3. Защита от параллельного выполнения
- Флаг
isProcessingгарантирует последовательное выполнение - Новые вызовы добавляются в очередь даже во время выполнения текущего
Пример использования
// Имитируем асинхронную функцию
async function apiCall(data) {
console.log('Выполняем с данными:', data);
await new Promise(resolve => setTimeout(resolve, 1000));
console.log('Завершено с данными:', data);
}
// Создаем обертку
const queuedApiCall = createQueueStackFn(apiCall);
// Быстрые последовательные вызовы
queuedApiCall('Запрос 1');
queuedApiCall('Запрос 2');
queuedApiCall('Запрос 3');
// Вывод:
// Выполняем с данными: Запрос 3
// (через 1 секунду)
// Завершено с данными: Запрос 3
// Вызовы 1 и 2 будут пропущены как устаревшие
Альтернативные подходы и оптимизации
Вариант с приоритетами
// Расширенная версия с приоритетами вызовов
function createPriorityQueueFn(fn, options = {}) {
const { priority = 'stack' } = options; // 'stack', 'queue', 'priority'
// ... реализация с разными стратегиями
}
Вариант с отменой запросов
// С поддержкой AbortController для отмены запросов
const controller = new AbortController();
queuedApiCall('data', { signal: controller.signal });
// Позже: controller.abort();
Практическое применение
Такая реализация полезна для:
- Оптимизации запросов к API - избегание лишних вызовов
- Обработки пользовательского ввода - например, поиск с дебаунсингом
- Синхронизации состояния - когда только последнее состояние актуально
- Управления анимациями - прерывание предыдущих анимаций
Производительность и нюансы
- Сложность: O(1) для добавления, O(n) для удаления устаревших вызовов
- Потребление памяти: растет линейно с количеством необработанных вызовов
- Ошибки: обрабатываются без прерывания всей очереди
Эта задача демонстрирует понимание:
- Асинхронного программирования в JavaScript
- Структур данных (очередь, стек)
- Функционального программирования (замыкания, функции высшего порядка)
- Управления состоянием и конкурентностью
Глубокая проработка таких задач показывает способность создавать надежные, эффективные абстракции для реальных приложений.