Skip to content

Commit 46e1962

Browse files
authored
Merge pull request #33 from dai-shi/use-reducer-based-implementation
Rewrite: useReducer based implementation
2 parents 808687a + d5f6eee commit 46e1962

File tree

1 file changed

+76
-54
lines changed

1 file changed

+76
-54
lines changed

src/use-async-task.js

Lines changed: 76 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,105 @@
11
import {
2-
useEffect,
2+
useLayoutEffect,
33
useReducer,
4+
useRef,
45
} from 'react';
5-
import { useMemoOne as useMemo } from 'use-memo-one';
66

7-
const createTask = (func, forceUpdate) => {
8-
const task = {
9-
abortController: null,
7+
const createTask = ({ func, dispatchRef }) => {
8+
const taskId = Symbol('TASK_ID');
9+
let abortController = null;
10+
return {
11+
func,
12+
taskId,
13+
runId: null,
1014
start: async (...args) => {
11-
if (task.id === null) {
12-
// already cleaned up
13-
return null;
15+
if (abortController) {
16+
abortController.abort();
1417
}
15-
task.abort();
16-
task.abortController = new AbortController();
17-
const taskId = Symbol('TASK_ID');
18-
task.id = taskId;
19-
task.started = true;
20-
task.pending = true;
21-
task.error = null;
22-
task.result = null;
23-
forceUpdate();
18+
abortController = new AbortController();
19+
const runId = Symbol('RUN_ID');
20+
dispatchRef.current({
21+
type: 'START',
22+
taskId,
23+
runId,
24+
});
2425
let result = null;
25-
let err = null;
26+
let error = null;
2627
try {
27-
result = await func(task.abortController, ...args);
28+
result = await func(abortController, ...args);
2829
} catch (e) {
2930
if (e.name !== 'AbortError') {
30-
err = e;
31+
error = e;
3132
}
3233
}
33-
if (task.id === taskId) {
34-
task.result = result;
35-
task.error = err;
36-
task.started = false;
37-
task.pending = false;
38-
forceUpdate();
39-
}
40-
if (err) throw err;
34+
dispatchRef.current({
35+
type: 'END',
36+
taskId,
37+
runId,
38+
result,
39+
error,
40+
});
41+
if (error) throw error;
4142
return result;
4243
},
4344
abort: () => {
44-
if (task.abortController) {
45-
task.abortController.abort();
46-
task.abortController = null;
45+
if (abortController) {
46+
abortController.abort();
47+
abortController = null;
4748
}
4849
},
49-
id: 0,
5050
started: false,
5151
pending: true,
5252
error: null,
5353
result: null,
5454
};
55-
return task;
55+
};
56+
57+
const reducer = (task, action) => {
58+
switch (action.type) {
59+
case 'INIT':
60+
return createTask(action);
61+
case 'START':
62+
if (task.taskId !== action.taskId) {
63+
return task; // bail out
64+
}
65+
return {
66+
...task,
67+
runId: action.runId,
68+
started: true,
69+
pending: true,
70+
error: null,
71+
result: null,
72+
};
73+
case 'END':
74+
if (task.taskId !== action.taskId || task.runId !== action.runId) {
75+
return task; // bail out
76+
}
77+
return {
78+
...task,
79+
started: false,
80+
pending: false,
81+
error: action.error,
82+
result: action.result,
83+
};
84+
default:
85+
throw new Error(`unknown action type: ${action.type}`);
86+
}
5687
};
5788

5889
export const useAsyncTask = (func) => {
59-
const [, forceUpdate] = useReducer(c => c + 1, 0);
60-
const task = useMemo(() => createTask(func, forceUpdate), [func]);
61-
useEffect(() => {
90+
const dispatchRef = useRef(() => { throw new Error('not initialized'); });
91+
const [task, dispatch] = useReducer(reducer, { func, dispatchRef }, createTask);
92+
useLayoutEffect(() => {
93+
if (task.func !== func) {
94+
dispatch({ type: 'INIT', func, dispatchRef });
95+
}
96+
});
97+
useLayoutEffect(() => {
98+
dispatchRef.current = dispatch;
6299
const cleanup = () => {
63-
task.id = null;
64-
task.abort();
100+
dispatchRef.current = () => {};
65101
};
66102
return cleanup;
67-
}, [task]);
68-
return useMemo(() => ({
69-
start: task.start,
70-
abort: task.abort,
71-
started: task.started,
72-
pending: task.pending,
73-
error: task.error,
74-
result: task.result,
75-
}), [
76-
task.start,
77-
task.abort,
78-
task.started,
79-
task.pending,
80-
task.error,
81-
task.result,
82-
]);
103+
}, []);
104+
return task;
83105
};

0 commit comments

Comments
 (0)