Apollo Client 的 Relay 订阅适配器:createFetchMultipartSubscription 深入解析
2026/9/20 11:29:39 网站建设 项目流程

Apollo Client 的 Relay 订阅适配器:createFetchMultipartSubscription 深入解析

【免费下载链接】apollo-clientThe industry-leading GraphQL client for TypeScript, JavaScript, React, Vue, Angular, and more. Apollo Client delivers powerful caching, intuitive APIs, and comprehensive developer tools to accelerate your app development.项目地址: https://gitcode.com/gh_mirrors/ap/apollo-client

本篇文章以 Apollo Client 仓库中的 API 报告 .api-reports/api-report-utilities_subscriptions_relay.api.md 为主线,系统讲解@apollo/client/utilities/subscriptions/relay子路径下createFetchMultipartSubscription的完整 API 签名、参数语义、内部实现原理与在 Relay 应用中的接入方式。读完本文,你将能够在 Relay 环境中利用 Apollo Client 的 HTTP 多部分(multipart)增量响应解析能力搭建订阅网络层,并理解其取消机制与错误处理边界。

一、背景:为什么需要 Relay 网络层适配器

GraphQL 规范并未规定订阅操作的传输协议。Apollo Client 在普通 Apollo 应用中通过HttpLink原生支持"基于 HTTP 的分块 multipart 响应"订阅(详见 docs/source/data/subscriptions.mdx 的 HTTP 小节),无需额外配置即可自动发送所需头部。

但当应用本身基于 Relay 生态(使用relay-runtimeEnvironmentNetwork)时,Relay 的网络层接口与 Apollo 的ApolloLink并不兼容——Relay 要求网络层提供一个形如(RequestParameters, Variables) => Observable<GraphQLResponse>的订阅执行函数。为此,Apollo Client 提供了面向 Relay 的网络层适配器,负责复用其 HTTP multipart 响应解析管线,这正是 API 报告所描述的createFetchMultipartSubscription的用途。

从仓库的包导出配置 package.json 可以看到,该模块以独立子路径对外暴露:

"./utilities/subscriptions/relay": "./src/utilities/subscriptions/relay/index.ts"

二、API 签名与类型定义

API 报告给出了该模块的核心导出与相关类型:

// @public export function createFetchMultipartSubscription( uri: string, { fetch: preferredFetch, headers }?: CreateMultipartSubscriptionOptions ): ( operation: RequestParameters, variables: OperationVariables ) => Observable<GraphQLResponse>; type CreateMultipartSubscriptionOptions = { fetch?: WindowOrWorkerGlobalScope["fetch"]; headers?: Record<string, string>; };

三个要点值得注意:

  • uri: string:订阅请求发送的目标 GraphQL HTTP 端点地址。
  • 可选配置对象:支持注入自定义fetch实现(例如用于 Node 环境或测试 mock)以及额外请求头headers。报告同时标注了ae-forgotten-export警告,说明CreateMultipartSubscriptionOptions属于未从入口类型文件导出的内部类型——它在实际使用中通常以内联对象字面量形式传入,不会造成问题。
  • 返回值:一个符合 RelayNetwork要求的订阅执行函数,其参数分别是relay-runtimeRequestParameters(来自relay-runtime)和@apollo/clientOperationVariables,返回relay-runtimeObservable<GraphQLResponse>

与 Relay 的集成示例

官方文档 docs/source/data/subscriptions.mdx 给出了完整接入示例,将适配器作为Network.create的第三个参数注入 Relay 环境:

import { createFetchMultipartSubscription } from "@apollo/client/utilities/subscriptions/relay"; import { Environment, Network, RecordSource, Store } from "relay-runtime"; const fetchMultipartSubs = createFetchMultipartSubscription( "https://api.example.com" ); const network = Network.create(fetchQuery, fetchMultipartSubs); export const RelayEnvironment = new Environment({ network, store: new Store(new RecordSource()), });

这里fetchQuery是你的查询/变更执行函数,fetchMultipartSubs则专职处理订阅操作。

三、内部实现原理

适配器的核心实现位于 src/utilities/subscriptions/relay/index.ts。整体流程可分为四个阶段。

1. 构造请求体与默认请求选项

const body: BaseHttpLink.Body = { operationName: operation.name, variables, query: operation.text || "", }; const options = generateOptionsForMultipartSubscription(headers || {});

请求体由 Relay 的RequestParameters映射而来:operationName取自operation.namequery取自operation.text(若文本为空则回落为空字符串),variables为调用方传入的变量对象。随后通过内部辅助函数generateOptionsForMultipartSubscription生成请求选项,其逻辑(源码同文件 L74-L87)如下:

function generateOptionsForMultipartSubscription( headers: Record<string, string> ) { const options = { ...fallbackHttpConfig.options, headers: { ...(headers || {}), ...fallbackHttpConfig.headers, accept: "multipart/mixed;boundary=graphql;subscriptionSpec=1.0,application/json", }, }; return options; }

fallbackHttpConfig来自 Apollo HTTP link 的公共配置 src/link/http/selectHttpOptionsAndBody.ts,其中:

  • 请求方法默认为POSTdefaultOptions);
  • 默认头包含accept: "application/graphql-response+json,application/json;q=0.9"content-type: "application/json"(同文件 L19-L34)。

适配器在合并用户传入的headers之后,会用multipart/mixed;boundary=graphql;subscriptionSpec=1.0,application/json覆盖accept头,明确告知服务器客户端期望 multipart 增量投递,同时也接受application/json作为普通响应回落。这与其他 Apollo HTTP link 对 multipart 请求的处理保持一致。

2. JSON 序列化与 fetch 发起

try { options.body = JSON.stringify(body); } catch (parseError) { sink.error(parseError as Error); return; }

请求体先进行JSON.stringify。若变量对象存在循环引用等无法序列化的情况,会直接向订阅sink抛出错误并终止——测试 src/utilities/subscriptions/relay/tests/createFetchMultipartSubscription.test.ts 专门验证了"JSON 序列化失败时不调用 fetch"这一行为。

随后选择实际的 fetch 实现:优先使用用户注入的preferredFetch,否则回退到全局fetch(通过maybe(() => fetch)的防御性包装访问),并将AbortControllersignal一并传入:

const currentFetch = preferredFetch || maybe(() => fetch) || backupFetch; currentFetch!(uri, { ...options, signal: controller.signal })

3. multipart 响应解析

收到响应后,适配器检查响应的content-type

const ctype = response.headers?.get("content-type"); if (ctype !== null && /^multipart\/mixed/i.test(ctype)) { return readMultipartBody(response, observerNext); } sink.error(new Error("Expected multipart response"));
  • 若响应为multipart/mixed,则委托给从 HTTP link 内部导入的readMultipartBody进行流式解析;
  • 否则视为协议不符,向sink抛出Expected multipart response错误。

readMultipartBody定义于 src/link/http/parseAndCheckHttpResponse.ts,是 Apollo HTTP link 共用的解析管线:它逐块消费 multipart body,对每个分块执行 JSON 解析,跳过空对象;对于 Apollo 格式的 payload 结果({ payload, errors? }),会将errors包装为CombinedProtocolErrors存入extensions后逐块推送给订阅者,从而以增量方式向 Relay 的Observable投递GraphQLResponse。这意味着服务器端渐进式交付(如@defer、流式增量)的每个 payload 都能实时送达订阅消费者。

4. 完成与取消(AbortController 语义)

.then(() => { sink.complete(); }) .catch((err: any) => { if (err.name !== "AbortError") { sink.error(err); } }); return () => { controller.abort(); };
  • 解析完成后调用sink.complete()正常结束订阅;
  • 任何非AbortError的错误都会通过sink.error抛出;
  • 订阅者调用unsubscribe()时,清理函数触发controller.abort(),从而中止底层 fetch 请求。

测试文件 createFetchMultipartSubscription.test.ts 对这套取消机制做了完整覆盖,可作为行为契约参考:

  • signal 传递fetch被调用时必带AbortSignal,且初始aborted === false(L16-L42);
  • 取消传播unsubscribe()后信号变为aborted === true(L44-L71);
  • AbortError 静默:因取消而导致的AbortError不会触发sink.error,避免"正常取消被当成异常"(L73-L104);
  • 非中止错误照常上报:普通的网络失败(如Network failure)仍会调用sink.error(L106-L128)。

四、配置参数详解

CreateMultipartSubscriptionOptions两个可选字段的语义归纳如下:

参数类型默认行为说明
fetchWindowOrWorkerGlobalScope["fetch"]全局fetch(按需兜底)自定义 fetch 实现,适用于 Node.js 服务端渲染、单元测试 mock 或带超时/重试的封装 fetch
headersRecord<string, string>空对象附加请求头,会与默认头合并;accept头由适配器强制设为 multipart 值,不可被覆盖

从源码结构看,headers的合并顺序为"用户头在前、默认头在后、accept最后强制写入",因此用户传入的accept会被 multipart 值覆盖;而content-type: application/jsonPOST方法由fallbackHttpConfig提供,与 Apollo HTTP link 的请求构造完全同源。

五、适用前提与使用限制

  • 服务器必须支持 multipart 订阅协议:适配器仅解析multipart/mixed响应,若端点返回普通 JSON 响应会以Expected multipart response报错,因此请先确认 GraphQL 端点支持增量投递(如 graphql-over-http 的 IncrementalDelivery 规范)。
  • 面向 Relay 运行时:函数的入参出参均为relay-runtime类型(RequestParametersObservable<GraphQLResponse>),仅适用于 Relay 的Network.create订阅参数位,不能直接作为 Apollo 的 link 使用。
  • 解析管线复用 HTTP link 实现readMultipartBodyfallbackHttpConfig均从src/link/http/内部导入,意味着该适配器与 Apollo HTTP link 的增量响应处理、错误类型(ServerErrorServerParseErrorCombinedProtocolErrors)行为保持一致,便于在混合技术栈中维持一致的订阅语义。

六、小结

createFetchMultipartSubscription是 Apollo Client 为 Relay 生态提供的轻量网络层适配器:它以极小的 API 表面(一个uri参数 + 两个可选配置项)复用了 Apollo 成熟的 HTTP multipart 解析管线,为 Relay 应用带来与原生 Apollo 一致的增量订阅体验,并通过AbortController实现了规范的取消语义。相关源码(src/utilities/subscriptions/relay/index.ts)、行为测试(src/utilities/subscriptions/relay/tests/createFetchMultipartSubscription.test.ts)与使用文档(docs/source/data/subscriptions.mdx)均在仓库中可查,是理解并落地该能力的完整参考。

【免费下载链接】apollo-clientThe industry-leading GraphQL client for TypeScript, JavaScript, React, Vue, Angular, and more. Apollo Client delivers powerful caching, intuitive APIs, and comprehensive developer tools to accelerate your app development.项目地址: https://gitcode.com/gh_mirrors/ap/apollo-client

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询