You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在useEffect中实现无限滚动时异步执行顺序异常求助

异步执行顺序问题解析与修复方案

为什么会出现这种执行顺序?

这是JavaScript异步事件循环机制的正常表现,和你写的getData包裹逻辑无关:

  1. 当async函数执行到await时,会暂停当前函数的执行,把函数剩余逻辑加入微任务队列,然后将控制权交回调用栈,优先执行后续的同步代码。
  2. 你的queryNews执行到await index.search时就会暂停,此时调用栈会继续执行useEffect里的console.log("4")、window.addEventListener、console.log("5")这些同步代码,等这些代码全部执行完,调用栈清空后,才会从微任务队列取出queryNews的剩余部分继续执行,所以queryNews3会在console.log("5")之后打印。
  3. 你用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 22:55:38