Thunks
Thunk — функция без аргументов, которая оборачивает вычисление для отложенного выполнения; в контексте Redux — функция-action creator, возвращающая функцию вместо объекта для обработки асинхронных операций.
Зачем нужно
Thunk — простейшая форма ленивых вычислений: вместо немедленного выполнения кода мы оборачиваем его в функцию и выполняем позже. В Redux это решает проблему async actions: стандартный Redux принимает только plain objects, а redux-thunk позволяет dispatch функций, которые сами делают async операции.
Где используется
- Redux middleware:
redux-thunkдля async action creators - Ленивые вычисления и кэширование результата
- Callback API (node-style
(err, result) => {}thunks) - Откладывание вычислений до момента потребления
Основной контент
Простой thunk
// Thunk — функция, оборачивающая значение для ленивого вычисления
function add(x, y) {
return => x + y; // thunk
}
const thunk = add(3, 4);
// Вычисление не произошло!
// Вычислить позже
console.log(thunk); // 7
console.log(thunk); // 7 — можно вызывать многократно
Мемоизирующий thunk
// Thunk с кэшированием результата (вычислить ровно один раз)
function memoThunk(fn) {
let evaluated = false;
let result;
return function {
if (!evaluated) {
result = fn;
evaluated = true;
}
return result;
};
}
const heavyCalc = memoThunk(() => {
console.log('Тяжёлое вычисление...');
return Array.from({ length: 1000 }, (_, i) => i).reduce((a, b) => a + b, 0);
});
console.log(heavyCalc); // 'Тяжёлое вычисление...' → 499500
console.log(heavyCalc); // → 499500 (без повторного вычисления)
Redux Thunk
// Обычный action creator — возвращает plain object
const increment = () => ({ type: 'INCREMENT' });
// Thunk action creator — возвращает функцию
const fetchUser = (userId) => async (dispatch, getState) => {
// Можем использовать dispatch и getState
dispatch({ type: 'FETCH_USER_REQUEST' });
try {
const response = await fetch(`/api/users/${userId}`);
const user = await response.json();
dispatch({ type: 'FETCH_USER_SUCCESS', payload: user });
} catch (error) {
dispatch({ type: 'FETCH_USER_FAILURE', error: error.message });
}
};
// Использование (с подключённым redux-thunk middleware)
store.dispatch(fetchUser(42));
// Минимальная реализация redux-thunk middleware
const thunkMiddleware = (store) => (next) => (action) => {
if (typeof action === 'function') {
return action(store.dispatch, store.getState);
}
return next(action);
};
Node-style thunk
// Thunk в node-style: функция, принимающая callback
function readFileThunk(path) {
return function(callback) {
fs.readFile(path, 'utf8', callback);
};
}
const readConfig = readFileThunk('./config.json');
// Вызываем позже с нужным callback
readConfig((err, data) => {
if (err) return console.error(err);
console.log(JSON.parse(data));
});
Thunk vs Promise
// Thunk: вычисление снаружи, передаётся функция
const getDataThunk = () => (dispatch) => {
fetch('/api/data')
.then(r => r.json())
.then(data => dispatch({ type: 'SET_DATA', data }));
};
// Promise/async-await: более современный подход
const fetchData = async () => {
const response = await fetch('/api/data');
return response.json();
};
// В Redux — лучше использовать redux-toolkit createAsyncThunk
Частые ошибки
- Thunk без
redux-thunkmiddleware — dispatch функции без middleware вызывает ошибку. Убедитесь, чтоapplyMiddleware(thunk)подключён. - Игнорирование возвращаемого значения — thunk action creator может возвращать Promise.
dispatch(fetchUser(1)).then(...)работает, если thunk возвращает Promise. - Вложенные thunks с дублирующей логикой — лучше выделить async логику в отдельные функции/сервисы, чем размещать весь код в thunk.