Skip to content

Commit 9fa5e48

Browse files
committed
Create AbortController with each promise and pass it along to the promiseFn/deferFn. Call abort() when starting a new promise.
1 parent 14268c5 commit 9fa5e48

File tree

10 files changed

+130
-9
lines changed

10 files changed

+130
-9
lines changed

examples/with-abortcontroller/.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
SKIP_PREFLIGHT_CHECK=true
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[![Deploy to now](https://deploy.now.sh/static/button.svg)](https://deploy.now.sh/?repo=https://github.com/ghengeveld/react-async/tree/master/examples/basic-fetch)
2+
3+
# Basic fetch with React Async
4+
5+
This demonstrates a very simple HTTP GET using `fetch`, wrapped with React Async.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"version": 2,
3+
"builds": [{ "src": "package.json", "use": "@now/static-build" }],
4+
"routes": [
5+
{ "src": "^/static/(.*)", "dest": "/static/$1" },
6+
{ "src": "^/favicon.ico", "dest": "/favicon.ico" },
7+
{ "src": "^/(.*)", "dest": "/index.html" }
8+
]
9+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"name": "basic-fetch",
3+
"version": "0.0.0",
4+
"private": true,
5+
"dependencies": {
6+
"react": "16.7.0-alpha.2",
7+
"react-async": "latest",
8+
"react-dom": "16.7.0-alpha.2",
9+
"react-scripts": "2.1.2"
10+
},
11+
"scripts": {
12+
"start": "react-scripts start",
13+
"build": "react-scripts build",
14+
"now-build": "npm run build && mv build dist"
15+
},
16+
"eslintConfig": {
17+
"extends": "react-app"
18+
},
19+
"browserslist": [
20+
">0.2%",
21+
"not dead",
22+
"not ie <= 11",
23+
"not op_mini all"
24+
]
25+
}
3.78 KB
Binary file not shown.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
6+
<meta name="theme-color" content="#000000" />
7+
<title>React App</title>
8+
</head>
9+
<body>
10+
<noscript> You need to enable JavaScript to run this app. </noscript>
11+
<div id="root"></div>
12+
</body>
13+
</html>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
body {
2+
margin: 20px;
3+
padding: 0;
4+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu",
5+
"Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
6+
-webkit-font-smoothing: antialiased;
7+
-moz-osx-font-smoothing: grayscale;
8+
}
9+
10+
button {
11+
background: none;
12+
color: palevioletred;
13+
border: 2px solid palevioletred;
14+
border-radius: 5px;
15+
padding: 10px 20px;
16+
font-size: 0.9em;
17+
font-weight: bold;
18+
outline: 0;
19+
text-transform: uppercase;
20+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import React from "react"
2+
import { useAsync } from "react-async"
3+
import ReactDOM from "react-dom"
4+
import "./index.css"
5+
6+
const download = (event, props, controller) =>
7+
fetch(`https://reqres.in/api/users/1?delay=3`, { signal: controller.signal })
8+
.then(res => (res.ok ? res : Promise.reject(res)))
9+
.then(res => res.json())
10+
11+
const App = () => {
12+
const { run, cancel, isLoading } = useAsync({ deferFn: download })
13+
return (
14+
<>
15+
{isLoading ? <button onClick={cancel}>cancel</button> : <button onClick={run}>start</button>}
16+
{isLoading ? (
17+
<p>Loading...</p>
18+
) : (
19+
<p>Inspect network traffic to see requests being canceled.</p>
20+
)}
21+
</>
22+
)
23+
}
24+
25+
ReactDOM.render(<App />, document.getElementById("root"))

src/index.js

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export const createInstance = (defaultProps = {}, displayName = "Async") => {
1414
constructor(props) {
1515
super(props)
1616

17+
this.start = this.start.bind(this)
1718
this.load = this.load.bind(this)
1819
this.run = this.run.bind(this)
1920
this.cancel = this.cancel.bind(this)
@@ -30,6 +31,7 @@ export const createInstance = (defaultProps = {}, displayName = "Async") => {
3031
this.mounted = false
3132
this.counter = 0
3233
this.args = []
34+
this.abortController = { abort: () => {} }
3335
this.state = {
3436
initialValue,
3537
data: initialData,
@@ -66,28 +68,39 @@ export const createInstance = (defaultProps = {}, displayName = "Async") => {
6668
this.mounted = false
6769
}
6870

71+
start() {
72+
if ("AbortController" in window) {
73+
this.abortController.abort()
74+
this.abortController = new window.AbortController()
75+
}
76+
this.counter++
77+
this.setState({ isLoading: true, startedAt: new Date(), finishedAt: undefined })
78+
}
79+
6980
load() {
7081
const promiseFn = this.props.promiseFn || defaultProps.promiseFn
7182
if (!promiseFn) return
72-
this.counter++
73-
this.setState({ isLoading: true, startedAt: new Date(), finishedAt: undefined })
74-
return promiseFn(this.props).then(this.onResolve(this.counter), this.onReject(this.counter))
83+
this.start()
84+
return promiseFn(this.props, this.abortController).then(
85+
this.onResolve(this.counter),
86+
this.onReject(this.counter)
87+
)
7588
}
7689

7790
run(...args) {
7891
const deferFn = this.props.deferFn || defaultProps.deferFn
7992
if (!deferFn) return
80-
this.counter++
8193
this.args = args
82-
this.setState({ isLoading: true, startedAt: new Date(), finishedAt: undefined })
83-
return deferFn(...args, this.props).then(
94+
this.start()
95+
return deferFn(...args, this.props, this.abortController).then(
8496
this.onResolve(this.counter),
8597
this.onReject(this.counter)
8698
)
8799
}
88100

89101
cancel() {
90102
this.counter++
103+
this.abortController.abort()
91104
this.setState({ isLoading: false, startedAt: undefined })
92105
}
93106

src/useAsync.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const useAsync = (opts, init) => {
44
const counter = useRef(0)
55
const isMounted = useRef(true)
66
const lastArgs = useRef(undefined)
7+
const abortController = useRef({ abort: () => {} })
78

89
const options = typeof opts === "function" ? { promiseFn: opts, initialValue: init } : opts
910
const { promiseFn, deferFn, initialValue, onResolve, onReject, watch } = options
@@ -35,6 +36,10 @@ const useAsync = (opts, init) => {
3536
const handleReject = count => error => count === counter.current && handleError(error, onReject)
3637

3738
const start = () => {
39+
if ("AbortController" in window) {
40+
abortController.current.abort()
41+
abortController.current = new window.AbortController()
42+
}
3843
counter.current++
3944
setState(state => ({
4045
...state,
@@ -47,15 +52,18 @@ const useAsync = (opts, init) => {
4752
const isPreInitialized = initialValue && counter.current === 0
4853
if (promiseFn && !isPreInitialized) {
4954
start()
50-
return promiseFn(options).then(handleResolve(counter.current), handleReject(counter.current))
55+
return promiseFn(options, abortController.current).then(
56+
handleResolve(counter.current),
57+
handleReject(counter.current)
58+
)
5159
}
5260
}
5361

5462
const run = (...args) => {
5563
if (deferFn) {
56-
start()
5764
lastArgs.current = args
58-
return deferFn(...args, options).then(
65+
start()
66+
return deferFn(...args, options, abortController.current).then(
5967
handleResolve(counter.current),
6068
handleReject(counter.current)
6169
)
@@ -64,6 +72,7 @@ const useAsync = (opts, init) => {
6472

6573
useEffect(() => load() && undefined, [promiseFn, watch])
6674
useEffect(() => () => (isMounted.current = false), [])
75+
useEffect(() => abortController.current.abort, [])
6776

6877
return useMemo(
6978
() => ({
@@ -74,6 +83,7 @@ const useAsync = (opts, init) => {
7483
reload: () => (lastArgs.current ? run(...lastArgs.current) : load()),
7584
cancel: () => {
7685
counter.current++
86+
abortController.current.abort()
7787
setState(state => ({ ...state, startedAt: undefined }))
7888
},
7989
setData: handleData,

0 commit comments

Comments
 (0)