在React Hooks中处理异步操作是每个开发者都会遇到的挑战。最近在项目中踩了一个关于Promise和async/await在自定义Hook中使用的大坑,分享给大家。
问题场景
我需要创建一个获取用户信息的自定义Hook,最初写法如下:
const useUserData = (userId) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
if (!userId) return;
// 错误写法:直接返回Promise
fetch(`/api/users/${userId}`)
.then(response => response.json())
.then(setData)
.catch(setError);
}, [userId]);
return { data, loading, error };
};
踩坑经历
这个看似正常的代码在实际使用中出现了严重问题:当用户快速切换时,会出现竞态条件(race condition),导致数据错乱。比如用户从ID=1切换到ID=2,但ID=1的请求后返回,会覆盖掉ID=2已经获取到的数据。
正确解决方案
通过查阅资料和踩坑总结,正确的做法应该是:
const useUserData = (userId) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
if (!userId) return;
// 使用AbortController处理取消请求
const abortController = new AbortController();
const fetchData = async () => {
try {
setLoading(true);
const response = await fetch(`/api/users/${userId}`, {
signal: abortController.signal
});
const result = await response.json();
// 只有在请求未被取消时才更新状态
if (!abortController.signal.aborted) {
setData(result);
}
} catch (err) {
if (!abortController.signal.aborted) {
setError(err);
}
} finally {
if (!abortController.signal.aborted) {
setLoading(false);
}
}
};
fetchData();
// 清理函数:组件卸载时取消请求
return () => {
abortController.abort();
};
}, [userId]);
return { data, loading, error };
};
关键要点
- 使用async/await替代Promise链,代码更清晰
- 引入AbortController处理请求取消
- 在清理函数中正确abort请求
- 添加条件判断避免竞态条件
这个坑踩得有点痛,但收获满满。大家在使用自定义Hook处理异步时一定要注意这些细节!

讨论