🌙 what - 什么是请求竞态?
比如: 在react组件中,组件接收一个props id
作为请求的参数,获取后端数据更新组件内容:
import React, { useEffect, useState } from 'react';
export default function DataDisplayer(props) {
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
const response = await fetch(`https://swapi.dev/api/people/${props.id}/`);
const newData = await response.json();
setData(newData);
};
fetchData();
}, [props.id]);
if (data) {
return <div>{data.name}</div>;
} else {
return null;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
如果组件接收的id
频繁切换,由于网络波动问题,新的id
已经展示,但是上一次的fetchData
响应数据晚于当前的fetchData
响应数据,就会导致当前的id与展示的内容不符。这个现象就是请求竞态问题
我们可以使用一个setTimeout
函数模拟网络超时:
export default function DataDisplayer(props) {
const [data, setData] = useState(null);
const [fetchedId, setFetchedId] = useState(null);
useEffect(() => {
const fetchData = async () => {
setTimeout(async () => {
const response = await fetch(
`https://swapi.dev/api/people/${props.id}/`
);
const newData = await response.json();
setFetchedId(props.id);
setData(newData);
}, Math.round(Math.random() * 12000));
};
fetchData();
}, [props.id]);
if (data) {
return (
<div>
<p style={{ color: fetchedId === props.id ? 'green' : 'red' }}>
Displaying Data for: {fetchedId}
</p>
<p>{data.name}</p>
</div>
);
} else {
return null;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
在多次切换id
的时候,之前的响应就很有可能晚于后续的响应,这样就可以促发这个bug了。
🌙 how - 解决方案
为了方便测试,下面的代码都增加了
setTimeout
延迟响应。
🌙 1.使用一个标志位清除状态:
useEffect(() => {
let active = true;
const fetchData = async () => {
setTimeout(async () => {
const response = await fetch(`https://swapi.dev/api/people/${props.id}/`);
const newData = await response.json();
if (active) {
setFetchedId(props.id);
setData(newData);
}
}, Math.round(Math.random() * 12000));
};
fetchData();
return () => {
active = false;
};
}, [props.id]);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
🌙 2.使用AbortController (opens new window)取消请求:
useEffect(() => {
const abortController = new AbortController();
const fetchData = async () => {
setTimeout(async () => {
try {
const response = await fetch(`https://swapi.dev/api/people/${id}/`, {
signal: abortController.signal,
});
const newData = await response.json();
setFetchedId(id);
setData(newData);
} catch (error) {
if (error.name === 'AbortError') {
// Aborting a fetch throws an error
// So we can't update state afterwards
}
// Handle other request errors here
}
}, Math.round(Math.random() * 12000));
};
fetchData();
return () => {
abortController.abort();
};
}, [id]);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
这样一来, 只要id
变化了,就会把上一次请求中的状态清除,保证每次的id
与响应都匹配。
Fixing Race Conditions in React with useEffect (opens new window)