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

Останется ли функция чистой при асинхронном запросе

1.8 Middle🔥 201 комментариев
#JavaScript Core

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

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

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

Может ли асинхронный запрос быть частью чистой функции?

Нет, функция, выполняющая асинхронный запрос (например, fetch, XMLHttpRequest, обращение к базе данных), по определению перестает быть чистой. Давайте разберем, почему это фундаментальное противоречие.

Что такое чистая функция (Pure Function)?

Чистая функция должна соответствовать двум ключевым критериям:

  1. Детерминированность: Для одних и тех же входных аргументов функция ВСЕГДА возвращает один и тот же результат.
  2. Отсутствие побочных эффектов (Side Effects): Функция не должна изменять внешнее состояние или взаимодействовать с внешним миром.
// Пример чистой функции
function add(a, b) {
    return a + b;
}
// add(2, 3) всегда вернет 5. Ничего снаружи не меняется.

Почему асинхронный запрос нарушает оба принципа?

1. Нарушение детерминированности

Результат асинхронного запроса зависит не от аргументов функции, а от состояния внешнего сервера, который является неконтролируемым входным данным.

// НЕчистая функция
async function getUserData(userId) {
    const response = await fetch(`/api/users/${userId}`);
    return response.json();
}

Вызов getUserData(123) в разные моменты времени может вернуть:

  • Актуальные данные пользователя
  • Ошибку 404, если пользователь удален
  • Ошибку 500, если упал сервер
  • Другие данные, если их изменили в БД

Одинаковые входные данные (userId = 123) приводят к разным результатам. Это нарушает детерминизм.

2. Нарушение принципа отсутствия побочных эффектов

Сам по себе факт выполнения HTTP-запроса — это глобальный побочный эффект. Функция:

  • Взаимодействует с внешней системой (сетевой стек, DNS, удаленный сервер).
  • Может создавать сетевые логи, влиять на счетчики запросов.
  • Изменяет состояние кэша браузера или операционной системы.
// Эта функция имеет очевидный побочный эффект - сетевой запрос
async function sendAnalytics(event) {
    // Этот вызов изменяет состояние внешнего сервера
    await fetch('/api/analytics', {
        method: 'POST',
        body: JSON.stringify(event)
    });
}

Архитектурные подходы для работы с асинхронностью

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

1. Разделение ответственности (Side Effects Isolation)

Чистая функция должна заниматься преобразованием данных, а асинхронные запросы нужно выносить на периферию системы.

// ЧИСТАЯ функция: только преобразует данные
function createUserMessage(user) {
    return `Добро пожаловать, ${user.name}! Ваш email: ${user.email}`;
}

// "Нечистая" функция на периферии (например, в слое сервиса)
async function fetchAndGreetUser(userId) {
    const response = await fetch(`/api/users/${userId}`);
    const user = await response.json();
    
    // Используем чистую функцию для преобразования
    return createUserMessage(user);
}

2. Использование контейнеров и монад (в продвинутых архитектурах)

Библиотеки вроде RxJS или подходы на основе монады IO/Either/Task позволяют отложить выполнение побочного эффекта и работать с ним как с абстракцией.

// Пример с RxJS: создание "описания" запроса как чистой операции
import { Observable } from 'rxjs';

function createUserObservable(userId) {
    // Сама функция чистая - она просто возвращает объект Observable
    return new Observable(subscriber => {
        // Побочный эффект инкапсулирован и отложен
        fetch(`/api/users/${userId}`)
            .then(response => response.json())
            .then(data => {
                subscriber.next(data);
                subscriber.complete();
            })
            .catch(err => subscriber.error(err));
    });
}

// Функция по-прежнему детерминирована: для одного userId
// всегда возвращает один и тот же Observable
const userStream = createUserObservable(123);

3. Dependency Injection (Внедрение зависимостей)

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

// Более тестируемая функция
async function getUserWithPosts(userId, fetchApi) {
    const user = await fetchApi(`/users/${userId}`);
    const posts = await fetchApi(`/posts?userId=${userId}`);
    return { ...user, posts };
}

// В продакшене передаем реальный fetch
await getUserWithPosts(123, fetch);

// В тестах передаем "стабильную" заглушку
const mockFetch = async (url) => ({ name: 'Test User' });
await getUserWithPosts(123, mockFetch); // Теперь детерминировано!

Практический вывод для собеседования

На собеседовании важно показать понимание иерархии предсказуемости:

  1. Синхронные чистые функции — максимальная предсказуемость, единичная ответственность
  2. Асинхронные функции с запросами — имеют побочные эффекты, требуют обработки ошибок, но их можно структурировать
  3. Ключевой компромисс: мы жертвуем "чистотой" ради практической полезности (без запросов веб-приложение невозможно), но компенсируем это:
    *   Выделением бизнес-логики в чистые функции
    *   Четким разделением слоев (UI, логика, side effects)
    *   Использованием состояний (Redux, MobX) для управления асинхронностью через middleware/sagas

Итог: Асинхронный запрос автоматически делает функцию нечистой, но грамотное архитектурное разделение позволяет изолировать побочные эффекты и сохранять основную кодовую базу предсказуемой и тестируемой.

Останется ли функция чистой при асинхронном запросе | PrepBro