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

Где хранится результат первого Promise при множественных вызовах?

2.4 Senior🔥 141 комментариев
#JavaScript Core#Оптимизация и производительность

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

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

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

Хранение результата Promise при множественных вызовах

Вопрос о том, где хранится результат первого Promise — это вопрос о Promise кэшировании и мемоизации. Это важно понимать для оптимизации производительности и предотвращения дублирующихся запросов.

Базовое поведение Promise

Promise сам по себе НЕ кэширует результаты — каждый вызов функции, возвращающей Promise, создаёт новый Promise:

// ПЛОХО - каждый вызов создаёт новый Promise
function fetchUser(id) {
  return fetch(`/api/users/${id}`).then(r => r.json());
}

const promise1 = fetchUser(1); // Новый Promise, новый fetch
const promise2 = fetchUser(1); // Новый Promise, новый fetch (дубль!)
const promise3 = fetchUser(1); // Новый Promise, новый fetch (ещё дубль!)

// Все три обращаются к серверу - неэффективно

Promise объект хранит:

  • State: pending, fulfilled, rejected
  • Value: результат (если fulfilled)
  • Reason: ошибка (если rejected)
// Однажды resolved Promise сохраняет значение
const promise = Promise.resolve(42);

promise.then(value => console.log(value)); // 42
promise.then(value => console.log(value)); // 42 (тот же результат)
promise.then(value => console.log(value)); // 42 (тот же результат)

// Результат 42 хранится внутри promise объекта

Promise Кэширование через замыкание

Решение: Кэшировать сам Promise объект

// ХОРОШО - кэшировать Promise в переменной
let cachedPromise = null;

function fetchUserCached(id) {
  if (!cachedPromise) {
    cachedPromise = fetch(`/api/users/${id}`).then(r => r.json());
  }
  return cachedPromise;
}

const promise1 = fetchUserCached(1); // Создан новый Promise, загружает с сервера
const promise2 = fetchUserCached(1); // Возвращает ТОТЖЕ Promise объект!
const promise3 = fetchUserCached(1); // Возвращает ТОТЖЕ Promise объект!

promise1 === promise2; // true - один и тот же объект!
promise2 === promise3; // true

// На сервер отправлен только один запрос

Результат где он хранится:

  1. В самом Promise объекте (в его internal slots)
  2. В переменной cachedPromise
  3. После resolve - в микротаске event loop
// Детально: где хранится значение
let cachedPromise = null;

function fetchUser(id) {
  if (!cachedPromise) {
    cachedPromise = fetch(`/api/users/${id}`)
      .then(r => r.json())
      .then(user => {
        // Значение user хранится в "fulfilled" состояния Promise
        // и в переменных всех .then() обработчиков
        return user;
      });
  }
  return cachedPromise; // Возвращаем Promise со значением
}

const promise = fetchUser(1);

promise.then(user => {
  // user = результат, хранящийся в Promise
  console.log('Пользователь:', user); // { id: 1, name: 'John' }
});

Паттерн: Мемоизация с Map

Более сложный случай: кэширование разных ID

// ХОРОШО - кэшировать разные Promise для разных ID
const requestCache = new Map();

function fetchUser(id) {
  // Проверить, есть ли уже Promise для этого ID
  if (!requestCache.has(id)) {
    const promise = fetch(`/api/users/${id}`).then(r => r.json());
    requestCache.set(id, promise);
  }
  return requestCache.get(id);
}

// Использование
fetchUser(1).then(user => console.log('User 1:', user)); // запрос 1
fetchUser(2).then(user => console.log('User 2:', user)); // запрос 2
fetchUser(1).then(user => console.log('User 1:', user)); // Нет запроса - из кэша!
fetchUser(1).then(user => console.log('User 1:', user)); // Нет запроса - из кэша!

// Результаты хранятся в:
// 1. Объекты Promise внутри Map
// 2. Internal slots каждого Promise ([[PromiseState]], [[PromiseResult]])

React: Где хранится результат

В React компоненте результат Promise можно хранить несколькими способами:

// Вариант 1: useState - результат в state
function UserComponent({ userId }: { userId: number }) {
  const [user, setUser] = useState(null); // Результат хранится в state
  
  useEffect(() => {
    fetchUser(userId).then(setUser); // Promise результат -> state
  }, [userId]);
  
  return <div>{user?.name}</div>;
}

// Вариант 2: useRef - результат в ref
function UserComponent({ userId }: { userId: number }) {
  const userRef = useRef(null); // Результат хранится в ref
  
  useEffect(() => {
    fetchUser(userId).then(user => {
      userRef.current = user; // Результат -> ref
    });
  }, [userId]);
  
  return <div>{userRef.current?.name}</div>;
}

// Вариант 3: Custom hook с кэшем
function useUserCache(userId) {
  const cache = useRef(new Map()); // Кэш Promise в ref
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    const cachedPromise = cache.current.get(userId);
    
    if (cachedPromise) {
      // Используем кэшированный Promise
      cachedPromise.then(setUser);
    } else {
      // Создаём новый Promise и кэшируем
      const promise = fetchUser(userId);
      cache.current.set(userId, promise);
      promise.then(setUser);
    }
  }, [userId]);
  
  return user;
}

Advanced: Мемоизация функций

Паттерн: Мемоизировать функцию, возвращающую Promise

function memoizePromise(fn) {
  const cache = new Map();
  
  return function(...args) {
    const key = JSON.stringify(args); // Ключ на основе аргументов
    
    if (!cache.has(key)) {
      const promise = fn(...args);
      cache.set(key, promise);
    }
    
    return cache.get(key); // Возвращаем кэшированный Promise
  };
}

// Использование
const memoizedFetch = memoizePromise((id) => 
  fetch(`/api/users/${id}`).then(r => r.json())
);

memoizedFetch(1).then(user => console.log(user)); // Запрос 1
memoizedFetch(1).then(user => console.log(user)); // Из кэша!
memoizedFetch(2).then(user => console.log(user)); // Запрос 2

Где физически хранятся данные

JavaScript двигатель (V8, SpiderMonkey) хранит результат Promise в:

// 1. Internal Slots (часть объекта Promise)
// [[PromiseState]]: "fulfilled" | "pending" | "rejected"
// [[PromiseResult]]: value или reason
// [[PromiseFulfillReactions]]: список обработчиков

const promise = Promise.resolve({ id: 1, name: 'John' });

// Внутри promise (не доступно напрямую):
// {
//   [[PromiseState]]: "fulfilled",
//   [[PromiseResult]]: { id: 1, name: 'John' }
// }

// Доступ к результату только через .then():
promise.then(result => {
  console.log(result); // { id: 1, name: 'John' }
});

Event Loop и результаты

Результат Promise попадает в Microtask Queue

const promise = fetch('/api/data').then(r => r.json());

// 1. fetch() вернул Promise (PENDING)
// 2. Браузер начал загружать данные
// 3. Когда ответ пришёл - Promise переходит в FULFILLED
// 4. Результат добавляется в Microtask Queue
// 5. Event Loop выполняет все обработчики из Microtask Queue
// 6. .then() вызовется с результатом

console.log('1'); // Выполнится сразу
promise.then(() => console.log('2')); // Выполнится из Microtask Queue
console.log('3'); // Выполнится после console.log('1')

// Вывод: 1, 3, 2

Best Practices

// 1. Использовать библиотеки для кэширования
import { useQuery } from '@tanstack/react-query';

function UserComponent({ userId }) {
  const { data: user } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId)
    // Результат автоматически кэшируется!
  });
  
  return <div>{user?.name}</div>;
}

// 2. Для простых случаев - мемоизировать Promise
const fetchUserMemo = memoizePromise(fetchUser);

// 3. Очищать кэш когда нужно
function clearUserCache(userId) {
  requestCache.delete(userId);
}

// 4. Использовать TTL (время жизни кэша)
function createCacheWithTTL(ttlMs) {
  const cache = new Map();
  
  return {
    set(key, promise) {
      cache.set(key, { promise, expiresAt: Date.now() + ttlMs });
    },
    get(key) {
      const item = cache.get(key);
      if (item && Date.now() < item.expiresAt) {
        return item.promise;
      }
      cache.delete(key);
      return null;
    }
  };
}

Итог

Результат Promise при множественных вызовах хранится в:

  1. Promise объект - internal slots ([[PromiseResult]], [[PromiseState]])
  2. Переменные замыкания - если кэшировать Promise в функции
  3. Map/Object - если использовать явный кэш
  4. React state - если использовать useState
  5. Библиотеки - React Query, SWR автоматически кэшируют

Основной принцип: Кэшируйте сам Promise объект, не результат. Promise сохраняет значение и все .then() вызовы получат одинаковый результат из одного Promise объекта.