← Назад к вопросам

Первую задачу будешь делать простую или сложную

2.0 Middle🔥 141 комментариев
#Soft Skills и рабочие процессы

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI4 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Задача про Функции, Очереди и Стек

Я выбираю сложную задачу, так как она позволяет продемонстрировать глубокое понимание JavaScript, принципов функционального программирования и структур данных. Решим классическую задачу на реализацию функции с очередью вызовов и поддержкой стека (LIFO).

Постановка задачи

Реализовать функцию createQueueStackFn, которая:

  1. Принимает другую функцию fn в качестве аргумента
  2. Возвращает новую функцию, которая при вызове добавляет аргументы в очередь
  3. Очередь обрабатывается последовательно, но с поддержкой стека последних вызовов (последний добавленный вызов имеет приоритет)
  4. Функция 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();

Практическое применение

Такая реализация полезна для:

  1. Оптимизации запросов к API - избегание лишних вызовов
  2. Обработки пользовательского ввода - например, поиск с дебаунсингом
  3. Синхронизации состояния - когда только последнее состояние актуально
  4. Управления анимациями - прерывание предыдущих анимаций

Производительность и нюансы

  • Сложность: O(1) для добавления, O(n) для удаления устаревших вызовов
  • Потребление памяти: растет линейно с количеством необработанных вызовов
  • Ошибки: обрабатываются без прерывания всей очереди

Эта задача демонстрирует понимание:

  • Асинхронного программирования в JavaScript
  • Структур данных (очередь, стек)
  • Функционального программирования (замыкания, функции высшего порядка)
  • Управления состоянием и конкурентностью

Глубокая проработка таких задач показывает способность создавать надежные, эффективные абстракции для реальных приложений.

Первую задачу будешь делать простую или сложную | PrepBro