Redux Generator Thunk
July 13, 2016 ยท View on GitHub
Generator thunk middleware for Redux.
npm install --save redux-generator-thunk
Motivation
Redux Generator Thunk middleware allows you to write action creators that return a generator function instead of an action.
When yielding a promise the generator will wait until the promises resolves before continuing. If the promise rejects the generator will throw an error (that can be caught using the usual try/catch syntax). This special treatment of promises makes asynchronous control flow much simpler (in a manner similar to the async/await proposal).
Any non-promise values that are yielded are dispatched. This allows you to yield simple actions, or yield other generator thunks.
The generator function receives getState as a parameter.
An action creator that performs an async action:
const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
const increment = () => {
return {
type: INCREMENT_COUNTER
};
};
const incrementAsync = () => {
return function* () {
yield delay(1000);
yield increment();
};
}
Installation
npm install --save redux-generator-thunk
Then, to enable Redux Generator Thunk, use applyMiddleware():
import { createStore, applyMiddleware } from 'redux';
import generatorThunk from 'redux-generator-thunk';
import rootReducer from './reducers/index';
// Note: this API requires redux@>=3.1.0
const store = createStore(
rootReducer,
applyMiddleware(generatorThunk)
);
Examples
Async
const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
const increment = () => ({
type: INCREMENT_COUNTER
});
const incrementAsync = () => {
return function* () {
yield delay(1000);
yield increment();
};
}
Yielding an action depending on the state
const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
const incrementIfOdd = () => {
return function* (getState) {
const { counter } = getState();
if (counter % 2 === 0) {
return;
}
yield increment();
};
}
Nested actions and generators
const requestPosts = reddit => ({
type: REQUEST_POSTS,
reddit
});
const receivePosts = (reddit, json) => {
type: RECEIVE_POSTS,
reddit,
posts: json.data
});
// A standard generator (not an action)
const getPostsGenerator = function* (reddit) {
const response = yield fetch(`https://www.reddit.com/r/${reddit}.json`);
};
const fetchPostsGenerator = reddit => {
return function* () {
yield requestPosts(reddit);
// Use `yield*` to call standard generators who can then
// wait for promises and dispatch actions themselves.
const json = yield* getPostsGenerator(reddit);
// Yield a standard action
yield receivePosts(reddit, json);
};
};
export const fetchPostsIfNeededGenerator = reddit => {
return function* (getState) {
const { posts } = getState();
if (!posts[reddit]) {
// We can yield generator thunks which will then run as normal.
yield fetchPostsGenerator(reddit);
}
};
};
Catching errors in promises
const fetchPostsGenerator = reddit => {
return function* () {
yield requestPosts(reddit);
let json;
try {
const response = yield fetch(`https://www.reddit.com/r/${reddit}.json`);
json = yield response.json();
} catch (err) {
yield requestPostsFailed(reddit, err);
return;
}
yield receivePosts(reddit, json);
};
};
License
MIT