在useEffect中实现无限滚动时异步执行顺序异常求助
异步执行顺序问题解析与修复方案
为什么会出现这种执行顺序?
这是JavaScript异步事件循环机制的正常表现,和你写的getData包裹逻辑无关:
- 当
async函数执行到await时,会暂停当前函数的执行,把函数剩余逻辑加入微任务队列,然后将控制权交回调用栈,优先执行后续的同步代码。 - 你的
queryNews执行到await index.search时就会暂停,此时调用栈会继续执行useEffect里的console.log("4")、window.addEventListener、console.log("5")这些同步代码,等这些代码全部执行完,调用栈清空后,才会从微任务队列取出queryNews的剩余部分继续执行,所以queryNews3会在console.log("5")之后打印。 - 你用
getData包裹queryNews并await,但getData()在useEffect里是直接调用的,没有被await(useEffect的回调不能是async函数,否则会返回Promise,违反React的规则),所以getData的执行不会阻塞后续同步代码的运行。
修复方案
如果你希望getData执行完成后再执行后续的同步代码(比如添加滚动监听),可以在useEffect内部定义一个异步初始化函数,把所有逻辑包裹进去:
const [articleState, setArticles] = useState<ArticleType[]>([]); useEffect(() => { console.log("1"); if (windowResized === "large" || windowResized === undefined) return; let isFetching = false; let isPaging = true; let paging = 0; console.log("2"); async function queryNews(input: string) { console.log("queryNews1"); isFetching = true; setIsLoading(true); setSearchState(true); setPageOnLoad(true); console.log("queryNews2"); const resp = await index.search(`${input}`, { page: paging, }); console.log("queryNews3"); const hits = resp?.hits as ArticleType[]; setTotalArticle(resp?.nbHits); console.log("queryNews4"); setArticles((prev) => [...prev, ...hits]); console.log("queryNews5"); setIsLoading(false); console.log("queryNews6"); paging = paging + 1; if (paging === resp?.nbPages) { isPaging = false; setScrolling(true); return; } console.log("queryNews7"); isFetching = false; setSearchState(false); setPageOnLoad(false); console.log("queryNews8"); } console.log("3"); async function scrollHandler(e: WheelEvent) { if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 100) { if (isFetching || !isPaging) return; console.log("scrollHandler"); await queryNews(keyword); } } const getData = async () => { await queryNews(keyword); }; // 异步初始化函数,包裹所有需要按顺序执行的逻辑 async function init() { await getData(); console.log("4"); window.addEventListener("wheel", scrollHandler); console.log("5"); } init(); return () => { window.removeEventListener("wheel", scrollHandler); }; }, [keyword, setSearchState, windowResized]);
修改后,控制台的执行顺序会符合你的预期:1→2→3→queryNews1→queryNews2→queryNews3→queryNews4→queryNews5→queryNews6→queryNews7→queryNews8→4→5(如果分页未到达总页数)。
另外补充两个注意点:
setArticles等状态更新是异步的,调用后不会立即更新articleState,组件会在状态更新后重新渲染,此时articleState.map会执行并打印"mobile",这部分会在queryNews5之后触发。- 永远不要把
useEffect的回调直接写成async函数,因为React期望useEffect返回清理函数或undefined,而async函数会返回Promise,会导致React警告,且清理逻辑无法正常执行。
内容的提问来源于stack exchange,提问作者Valerie Wang
相关产品推荐
相关产品推荐

