1 |
|
2 | import {
|
3 | call,
|
4 | cancel,
|
5 | fork,
|
6 | take,
|
7 | put,
|
8 | all,
|
9 | takeLatest,
|
10 | select,
|
11 | } from "redux-saga/effects";
|
12 | import { normalize } from "normalizr";
|
13 | import {
|
14 | isRequest,
|
15 | isRefresh,
|
16 | successOf,
|
17 | failureOf,
|
18 | requestOf,
|
19 | } from "./actions";
|
20 | import { humps } from "./lib";
|
21 | import { makeStateSelector } from "./selector";
|
22 | import { Apis } from "./apis";
|
23 |
|
24 |
|
25 | const sagaHooks = {
|
26 | beforeRequest: null,
|
27 | afterRequest: null,
|
28 | };
|
29 |
|
30 | export function registerHooks(hooks = {}) {
|
31 | sagaHooks.beforeRequest = hooks.beforeRequest || null;
|
32 | sagaHooks.afterRequest = hooks.afterRequest || null;
|
33 | }
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 | export function* callAPI(key, action, meta) {
|
42 | if (!key) return;
|
43 | if (!action) return;
|
44 |
|
45 | const { payload } = action;
|
46 | const api = Apis.get(key);
|
47 |
|
48 | if (!api) {
|
49 | throw new Error(`redux-api: api ${key} should be registered`);
|
50 | }
|
51 |
|
52 | const endpoint = api.endpoint;
|
53 | if (!endpoint) {
|
54 | throw new Error(`redux-api: api ${key} should has endpoint`);
|
55 | }
|
56 |
|
57 | const schema = api.schema;
|
58 |
|
59 | if (sagaHooks.beforeRequest) {
|
60 | const passed = yield call(sagaHooks.beforeRequest, key, action, api);
|
61 |
|
62 | if (!passed) {
|
63 | return;
|
64 | }
|
65 | }
|
66 |
|
67 | let epResult;
|
68 | let epError;
|
69 | try {
|
70 | epResult = (yield call(endpoint, payload)) || {};
|
71 | const { body = {}, headers = {} } = epResult;
|
72 | const data = schema ? normalize(body, schema) : { result: body };
|
73 |
|
74 | yield put({
|
75 | type: successOf(key),
|
76 | payload: { ...data, ...humps(headers) },
|
77 | key,
|
78 | meta,
|
79 | });
|
80 | } catch (err) {
|
81 | yield put({
|
82 | type: failureOf(key),
|
83 | error: err,
|
84 | key,
|
85 | meta,
|
86 | });
|
87 | epError = err;
|
88 | }
|
89 |
|
90 | if (sagaHooks.afterRequest) {
|
91 | yield call(sagaHooks.afterRequest, key, action, api, epResult, epError);
|
92 | }
|
93 | }
|
94 |
|
95 | export function* watchRequest() {
|
96 | const tasks = {};
|
97 | while (true) {
|
98 | const action = yield take(isRequest);
|
99 | const { meta = {}, key } = action;
|
100 | if (tasks[key]) yield cancel(tasks[key]);
|
101 | tasks[key] = yield fork(callAPI, key, action, meta);
|
102 | }
|
103 | }
|
104 |
|
105 | export function* watchRefresh(action) {
|
106 |
|
107 | const { key } = action;
|
108 | const api = Apis.get(key);
|
109 | if (!api) {
|
110 | throw new Error(`redux-api: api ${key} should be registered`);
|
111 | }
|
112 | const stateSelector = makeStateSelector(api.reduxPath);
|
113 | const apiState = yield select(stateSelector);
|
114 |
|
115 |
|
116 | yield put({
|
117 | type: requestOf(key),
|
118 | key,
|
119 | payload: apiState.request,
|
120 | meta: apiState.meta,
|
121 | });
|
122 |
|
123 |
|
124 | }
|
125 |
|
126 | export function* watchApis() {
|
127 | yield all([fork(watchRequest), takeLatest(isRefresh, watchRefresh)]);
|
128 | }
|