Где хранится результат первого Promise при множественных вызовах?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Хранение результата 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
// На сервер отправлен только один запрос
Результат где он хранится:
- В самом Promise объекте (в его internal slots)
- В переменной cachedPromise
- После 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 при множественных вызовах хранится в:
- Promise объект - internal slots ([[PromiseResult]], [[PromiseState]])
- Переменные замыкания - если кэшировать Promise в функции
- Map/Object - если использовать явный кэш
- React state - если использовать useState
- Библиотеки - React Query, SWR автоматически кэшируют
Основной принцип: Кэшируйте сам Promise объект, не результат. Promise сохраняет значение и все .then() вызовы получат одинаковый результат из одного Promise объекта.