Skip to content

Commit b887e31

Browse files
committed
feat: add bindAsyncAction helper for redux-saga
1 parent a022f66 commit b887e31

17 files changed

+2418
-39
lines changed

.gitignore

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,6 @@
55
# NPM
66
node_modules
77
npm-*.log
8-
yarn.lock
9-
10-
tests-build
118

129
# OS X
1310
.DS_Store

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@ node_js:
55

66
script:
77
- npm run lint
8-
- npm run build
98
- npm test
9+
- npm run build
File renamed without changes.

es/index.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
export function isType(action, actionCreator) {
2+
return action.type === actionCreator.type;
3+
}
4+
export default function actionCreatorFactory(prefix, defaultIsError = p => p instanceof Error) {
5+
const actionTypes = {};
6+
const base = prefix ? `${prefix}/` : "";
7+
function actionCreator(type, commonMeta, isError = defaultIsError) {
8+
const fullType = base + type;
9+
if (process.env.NODE_ENV !== 'production') {
10+
if (actionTypes[fullType])
11+
throw new Error(`Duplicate action type: ${fullType}`);
12+
actionTypes[fullType] = true;
13+
}
14+
return Object.assign((payload, meta) => {
15+
const action = {
16+
type: fullType,
17+
payload,
18+
};
19+
if (commonMeta || meta) {
20+
action.meta = Object.assign({}, commonMeta, meta);
21+
}
22+
if (isError && (typeof isError === 'boolean' || isError(payload))) {
23+
action.error = true;
24+
}
25+
return action;
26+
}, { type: fullType });
27+
}
28+
function asyncActionCreators(type, commonMeta) {
29+
return {
30+
type: base + type,
31+
started: actionCreator(`${type}_STARTED`, commonMeta, false),
32+
done: actionCreator(`${type}_DONE`, commonMeta, false),
33+
failed: actionCreator(`${type}_FAILED`, commonMeta, true),
34+
};
35+
}
36+
return Object.assign(actionCreator, { async: asyncActionCreators });
37+
}

es/sagas.d.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { AsyncActionCreators } from "./index";
2+
import { SagaIterator } from "redux-saga";
3+
export declare function bindAsyncAction<R>(actionCreators: AsyncActionCreators<void, R, any>): {
4+
(worker: () => Promise<R> | SagaIterator): () => SagaIterator;
5+
(worker: (params: void) => Promise<R> | SagaIterator): (params: void) => SagaIterator;
6+
<A1>(worker: (params: void, arg1: A1) => Promise<R> | SagaIterator): (params: void, arg1: A1) => SagaIterator;
7+
<A1, A2>(worker: (params: void, arg1: A1, arg2: A2) => Promise<R> | SagaIterator): (params: void, arg1: A1, arg2: A2) => SagaIterator;
8+
<A1, A2, A3>(worker: (params: void, arg1: A1, arg2: A2, arg3: A3, ...rest: any[]) => Promise<R> | SagaIterator): (params: void, arg1: A1, arg2: A2, arg3: A3, ...rest: any[]) => SagaIterator;
9+
};
10+
export declare function bindAsyncAction<P, R>(actionCreators: AsyncActionCreators<P, R, any>): {
11+
(worker: (params: P) => Promise<R> | SagaIterator): (params: P) => SagaIterator;
12+
<A1>(worker: (params: P, arg1: A1) => Promise<R> | SagaIterator): (params: P, arg1: A1) => SagaIterator;
13+
<A1, A2>(worker: (params: P, arg1: A1, arg2: A2) => Promise<R> | SagaIterator): (params: P, arg1: A1, arg2: A2) => SagaIterator;
14+
<A1, A2, A3>(worker: (params: P, arg1: A1, arg2: A2, arg3: A3, ...rest: any[]) => Promise<R> | SagaIterator): (params: P, arg1: A1, arg2: A2, arg3: A3, ...rest: any[]) => SagaIterator;
15+
};

es/sagas.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { put, call, cancelled } from "redux-saga/effects";
2+
export function bindAsyncAction(actionCreator) {
3+
return (worker) => {
4+
function* boundAsyncActionSaga(params, ...args) {
5+
yield put(actionCreator.started(params));
6+
try {
7+
const result = yield call(worker, params, ...args);
8+
yield put(actionCreator.done({ params, result }));
9+
return result;
10+
}
11+
catch (error) {
12+
yield put(actionCreator.failed({ params, error }));
13+
throw error;
14+
}
15+
finally {
16+
if (yield cancelled()) {
17+
yield put(actionCreator.failed({ params, error: 'cancelled' }));
18+
}
19+
}
20+
}
21+
const capName = worker.name.charAt(0).toUpperCase() +
22+
worker.name.substring(1);
23+
return setFunctionName(boundAsyncActionSaga, `bound${capName}(${actionCreator.type})`);
24+
};
25+
}
26+
/**
27+
* Set function name.
28+
*
29+
* Note that this won't have effect on built-in Chrome stack traces, although
30+
* useful for traces generated by `redux-saga`.
31+
*/
32+
function setFunctionName(func, name) {
33+
try {
34+
Object.defineProperty(func, 'name', {
35+
value: name,
36+
configurable: true,
37+
});
38+
}
39+
catch (e) {
40+
}
41+
return func;
42+
}

lib/index.d.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { Action as ReduxAction } from "redux";
2+
export interface Action<P> extends ReduxAction {
3+
type: string;
4+
payload: P;
5+
error?: boolean;
6+
meta?: Object;
7+
}
8+
export interface Success<P, S> {
9+
params: P;
10+
result: S;
11+
}
12+
export interface Failure<P, E> {
13+
params: P;
14+
error: E;
15+
}
16+
export declare function isType<P>(action: ReduxAction, actionCreator: ActionCreator<P>): action is Action<P>;
17+
export interface ActionCreator<P> {
18+
type: string;
19+
(payload: P, meta?: Object): Action<P>;
20+
}
21+
export interface EmptyActionCreator extends ActionCreator<undefined> {
22+
(payload?: undefined, meta?: Object): Action<undefined>;
23+
}
24+
export interface AsyncActionCreators<P, S, E> {
25+
type: string;
26+
started: ActionCreator<P>;
27+
done: ActionCreator<Success<P, S>>;
28+
failed: ActionCreator<Failure<P, E>>;
29+
}
30+
export interface ActionCreatorFactory {
31+
(type: string, commonMeta?: Object, error?: boolean): EmptyActionCreator;
32+
<P>(type: string, commonMeta?: Object, isError?: boolean): ActionCreator<P>;
33+
<P>(type: string, commonMeta?: Object, isError?: (payload: P) => boolean): ActionCreator<P>;
34+
async<P, S>(type: string, commonMeta?: Object): AsyncActionCreators<P, S, any>;
35+
async<P, S, E>(type: string, commonMeta?: Object): AsyncActionCreators<P, S, E>;
36+
}
37+
export default function actionCreatorFactory(prefix?: string, defaultIsError?: (payload: any) => boolean): ActionCreatorFactory;

lib/index.js

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,32 @@
1-
"use strict";
1+
'use strict';
2+
3+
Object.defineProperty(exports, "__esModule", {
4+
value: true
5+
});
6+
exports.isType = isType;
7+
exports.default = actionCreatorFactory;
28
function isType(action, actionCreator) {
39
return action.type === actionCreator.type;
410
}
5-
exports.isType = isType;
6-
function actionCreatorFactory(prefix, defaultIsError) {
7-
if (defaultIsError === void 0) { defaultIsError = function (p) { return p instanceof Error; }; }
11+
function actionCreatorFactory(prefix) {
12+
var defaultIsError = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function (p) {
13+
return p instanceof Error;
14+
};
15+
816
var actionTypes = {};
9-
var base = prefix ? prefix + "/" : "";
10-
function actionCreator(type, commonMeta, isError) {
11-
if (isError === void 0) { isError = defaultIsError; }
17+
var base = prefix ? prefix + '/' : "";
18+
function actionCreator(type, commonMeta) {
19+
var isError = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : defaultIsError;
20+
1221
var fullType = base + type;
1322
if (process.env.NODE_ENV !== 'production') {
14-
if (actionTypes[fullType])
15-
throw new Error("Duplicate action type: " + fullType);
23+
if (actionTypes[fullType]) throw new Error('Duplicate action type: ' + fullType);
1624
actionTypes[fullType] = true;
1725
}
1826
return Object.assign(function (payload, meta) {
1927
var action = {
2028
type: fullType,
21-
payload: payload,
29+
payload: payload
2230
};
2331
if (commonMeta || meta) {
2432
action.meta = Object.assign({}, commonMeta, meta);
@@ -32,12 +40,10 @@ function actionCreatorFactory(prefix, defaultIsError) {
3240
function asyncActionCreators(type, commonMeta) {
3341
return {
3442
type: base + type,
35-
started: actionCreator(type + "_STARTED", commonMeta, false),
36-
done: actionCreator(type + "_DONE", commonMeta, false),
37-
failed: actionCreator(type + "_FAILED", commonMeta, true),
43+
started: actionCreator(type + '_STARTED', commonMeta, false),
44+
done: actionCreator(type + '_DONE', commonMeta, false),
45+
failed: actionCreator(type + '_FAILED', commonMeta, true)
3846
};
3947
}
4048
return Object.assign(actionCreator, { async: asyncActionCreators });
41-
}
42-
Object.defineProperty(exports, "__esModule", { value: true });
43-
exports.default = actionCreatorFactory;
49+
}

lib/sagas.d.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { AsyncActionCreators } from "./index";
2+
import { SagaIterator } from "redux-saga";
3+
export declare function bindAsyncAction<R>(actionCreators: AsyncActionCreators<void, R, any>): {
4+
(worker: () => Promise<R> | SagaIterator): () => SagaIterator;
5+
(worker: (params: void) => Promise<R> | SagaIterator): (params: void) => SagaIterator;
6+
<A1>(worker: (params: void, arg1: A1) => Promise<R> | SagaIterator): (params: void, arg1: A1) => SagaIterator;
7+
<A1, A2>(worker: (params: void, arg1: A1, arg2: A2) => Promise<R> | SagaIterator): (params: void, arg1: A1, arg2: A2) => SagaIterator;
8+
<A1, A2, A3>(worker: (params: void, arg1: A1, arg2: A2, arg3: A3, ...rest: any[]) => Promise<R> | SagaIterator): (params: void, arg1: A1, arg2: A2, arg3: A3, ...rest: any[]) => SagaIterator;
9+
};
10+
export declare function bindAsyncAction<P, R>(actionCreators: AsyncActionCreators<P, R, any>): {
11+
(worker: (params: P) => Promise<R> | SagaIterator): (params: P) => SagaIterator;
12+
<A1>(worker: (params: P, arg1: A1) => Promise<R> | SagaIterator): (params: P, arg1: A1) => SagaIterator;
13+
<A1, A2>(worker: (params: P, arg1: A1, arg2: A2) => Promise<R> | SagaIterator): (params: P, arg1: A1, arg2: A2) => SagaIterator;
14+
<A1, A2, A3>(worker: (params: P, arg1: A1, arg2: A2, arg3: A3, ...rest: any[]) => Promise<R> | SagaIterator): (params: P, arg1: A1, arg2: A2, arg3: A3, ...rest: any[]) => SagaIterator;
15+
};

lib/sagas.js

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
'use strict';
2+
3+
Object.defineProperty(exports, "__esModule", {
4+
value: true
5+
});
6+
exports.bindAsyncAction = bindAsyncAction;
7+
8+
var _effects = require('redux-saga/effects');
9+
10+
function bindAsyncAction(actionCreator) {
11+
return function (worker) {
12+
var _marked = [boundAsyncActionSaga].map(regeneratorRuntime.mark);
13+
14+
function boundAsyncActionSaga(params) {
15+
var _len,
16+
args,
17+
_key,
18+
result,
19+
_args = arguments;
20+
21+
return regeneratorRuntime.wrap(function boundAsyncActionSaga$(_context) {
22+
while (1) {
23+
switch (_context.prev = _context.next) {
24+
case 0:
25+
_context.next = 2;
26+
return (0, _effects.put)(actionCreator.started(params));
27+
28+
case 2:
29+
_context.prev = 2;
30+
31+
for (_len = _args.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
32+
args[_key - 1] = _args[_key];
33+
}
34+
35+
_context.next = 6;
36+
return _effects.call.apply(undefined, [worker, params].concat(args));
37+
38+
case 6:
39+
result = _context.sent;
40+
_context.next = 9;
41+
return (0, _effects.put)(actionCreator.done({ params: params, result: result }));
42+
43+
case 9:
44+
return _context.abrupt('return', result);
45+
46+
case 12:
47+
_context.prev = 12;
48+
_context.t0 = _context['catch'](2);
49+
_context.next = 16;
50+
return (0, _effects.put)(actionCreator.failed({ params: params, error: _context.t0 }));
51+
52+
case 16:
53+
throw _context.t0;
54+
55+
case 17:
56+
_context.prev = 17;
57+
_context.next = 20;
58+
return (0, _effects.cancelled)();
59+
60+
case 20:
61+
if (!_context.sent) {
62+
_context.next = 23;
63+
break;
64+
}
65+
66+
_context.next = 23;
67+
return (0, _effects.put)(actionCreator.failed({ params: params, error: 'cancelled' }));
68+
69+
case 23:
70+
return _context.finish(17);
71+
72+
case 24:
73+
case 'end':
74+
return _context.stop();
75+
}
76+
}
77+
}, _marked[0], this, [[2, 12, 17, 24]]);
78+
}
79+
var capName = worker.name.charAt(0).toUpperCase() + worker.name.substring(1);
80+
return setFunctionName(boundAsyncActionSaga, 'bound' + capName + '(' + actionCreator.type + ')');
81+
};
82+
}
83+
/**
84+
* Set function name.
85+
*
86+
* Note that this won't have effect on built-in Chrome stack traces, although
87+
* useful for traces generated by `redux-saga`.
88+
*/
89+
function setFunctionName(func, name) {
90+
try {
91+
Object.defineProperty(func, 'name', {
92+
value: name,
93+
configurable: true
94+
});
95+
} catch (e) {}
96+
return func;
97+
}

0 commit comments

Comments
 (0)