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

Redux Toolkit Query查询未并行执行 如何让两个请求同时加载完成

问题场景

编写了如下前端组件代码:

import { useGetPostQuery, useGetPostFnQuery } from "./store/postApi";

export function App() {
  const { data, isLoading } = useGetPostQuery(1);
  const result = useGetPostFnQuery(1);
  console.log("result", result);
  return (
    <div>
      {isLoading ? <div>loading...</div> : <div>{data.post}</div>}
      {result.isLoading ? <div>loading...</div> : <div>{result.data.post}</div>}
    </div>
  );
}

对应./store/postApi.js的RTK Query实现:

import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";

export const postApi = createApi({
  reducerPath: "postApi",
  baseQuery: fetchBaseQuery({ baseUrl: "https://webcodingcenter.com/shared/" }),
  endpoints: (builder) => ({
    getPost: builder.query({
      query: (id) => `get_post.php?id=${id}`,
      transformResponse: (response, meta, arg) => {
        console.log("meta", meta);
        return { post: response.post.toLowerCase() };
      }
    }),
    getPostFn: builder.query({
      queryFn(arg, queryApi, extraOptions, baseQuery) {
        console.log("queryApi", queryApi);
        if (Math.random() < 0.9) {
          return fetch(
            `https://webcodingcenter.com/shared/${extraOptions.file}?id=${arg}`
          )
            .then((r) => r.json())
            .then((j) => ({ data: j }));
        } else
          return {
            error: {
              status: 500,
              statusText: "Internal Server Error",
              data: "Just a simulated error!"
            }
          };
      },
      extraOptions: { file: "get_post.php" }
    }
  })
});

export const { useGetPostQuery, useGetPostFnQuery } = postApi;

后端接口设置了1秒响应延迟:

<?php
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, PUT, POST, DELETE, OPTIONS, post, get');
header("Access-Control-Max-Age", "3600");
header('Access-Control-Allow-Headers: Origin, Content-Type, X-Auth-Token');
header("Access-Control-Allow-Credentials", "true");
sleep(1);
echo json_encode(array("post"=>file_get_contents("data".$_GET["id"].".txt")));
?>

预期页面中两行文本同时加载完成,但实际queryFn实现的接口返回的内容总是先加载完成。

问题原因

两个请求加载不同步的核心原因是二者走了完全不同的请求发起逻辑,触发时机存在微任务级时间差:

  • 走query配置的普通查询,会完整经过RTK Query中间件管道、fetchBaseQuery参数拼接、请求信号绑定、状态批量调度等流程,最终发起请求的时机在当前渲染周期的微任务队列末尾
  • 走queryFn配置的查询,代码中直接裸调用原生fetch,请求会在queryFn被中间件调用的瞬间同步发起,比普通查询早几到几十毫秒触发
  • 后端接口对每个请求独立执行1秒sleep延迟,早发起的请求自然更早返回,因此出现第二行内容先加载的现象。

另外这种裸写fetch的写法本身存在缺陷:

  • 绕过了fetchBaseQuery的统一配置,包括公共请求头、鉴权参数、baseUrl、全局拦截逻辑等
  • 未绑定RTK Query自动生成的AbortSignal,组件卸载时请求无法自动取消,存在内存泄漏风险
  • 错误处理逻辑和普通查询不统一,后续维护成本高
解决方案

不要在queryFn中裸写原生fetch,优先复用RTK Query传入的baseQuery方法发起请求是官方推荐的最佳实践。修改后两个查询将走完全一致的请求调度逻辑,保证两个请求几乎同时发起、同步返回。
修改后的getPostFn配置代码如下:

getPostFn: builder.query({
  async queryFn(arg, queryApi, extraOptions, baseQuery) {
    console.log("queryApi", queryApi);
    if (Math.random() < 0.9) {
      // 传参格式和普通query的query返回值一致,自动复用所有baseQuery配置
      const result = await baseQuery(`${extraOptions.file}?id=${arg}`);
      if (result.error) return { error: result.error };
      return { data: result.data };
    }
    return {
      error: {
        status: 500,
        statusText: "Internal Server Error",
        data: "Just a simulated error!"
      }
    };
  },
  extraOptions: { file: "get_post.php" }
})

修改后两个请求会在同一轮调度中发起,时间差在10毫秒以内,对应1秒的后端延迟,返回时间差几乎无法感知,即可实现两行内容同步加载的效果。

内容的提问来源于stack exchange,提问作者Chong Lip Phang

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 19:12:30