前端开发者进阶指南:从 ArcGIS 源码中学习企业级网络请求封装(二)

203 阅读6分钟

ArcGIS JSAPI in depth 之网络请求封装

基于JavaScript的网络请求封装可以基于多种不同的底层网络请求API或流行的请求库来完成,包括fetch、axios、superagent、XMLHttpRequest等。ArcGIS底层基于fetch API来封装网络请求。

  XMLHttpRequest(XHR)是早期 Web 开发中用于异步请求的核心技术,但它存在一些明显的缺点,比如API设计复杂、缺乏流式处理、错误处理机制简单等。随着 Web 应用的复杂度增加,fetch API作为一种替代方案被提出,2015年以后,Fetch即被纳入W3C的标准化进程,并且在2017年以来得到了主流浏览器的广泛支持,成为前端主流的开发工具之一。相比传统的XHR,Fetch API引入了Request/Response对象类型,支持CORS跨域请求、流式数据处理并且具备更细粒度的请求控制。

  Fetch API具备了现代网络请求API所需要的多种底层技术能力,使得开发人员可以专注于程序业务逻辑的处理,而无需过度关注各种复杂的网络状态以及不同浏览器之间的差异。然而,对于复杂的Web应用程序而言,标准的Fetch API仍然缺乏一些常用的高阶网络请求编排能力,例如重试、超时取消、网络代理等等。ArcGIS在原生的Fetch API基础之上进行了封装,并根据 GIS 应用自身的特点,封装了大量的高级能力。

  在前一章节中,我们梳理了请求处理的6个简要步骤,其中第五步发出网络请求是通过核心辅助函数doRequest完成的。该函数负责实际发送请求、处理重试逻辑、身份验证、代理配置以及响应解析。

函数签名

  • async function doRequest(requestOptions) { // 返回值: 包含响应数据、HTTP 状态、请求配置等信息的对象 }

处理流程


  函数采用以下的核心处理流程:

开始
│
├─ 调用 initRequest(requestOptions) 初始化请求配置
│ ├─ 设置 fetchOptions(方法、头、超时等)
│ ├─ 处理身份验证(API 密钥、令牌)
│ └─ 处理请求体(FormData、字符串)
│
├─ do-while 循环
│ ├─ 调用 sendRequest(fetchOptions) 发送请求
│ │ ├─ 处理代理和 URL
│ │ ├─ 发送 fetch 请求
│ │ └─ 解析响应数据
│ │
│ └─ 调用 needRetry(e, r, s) 判断是否需要重试
│ ├─ 身份验证失败 → 获取新凭证并重试
│ └─ SSL 错误 → 启用 SSL 并重试
│
├─ 错误处理
│ ├─ 生成标准错误对象
│ └─ 调用拦截器的 error 回调
│
├─ 后续处理
│ ├─ 更新信任服务器列表
│ └─ 处理凭证资源
│

└─ 返回响应结果

1. 初始化fetch请求配置

  通过initRequest函数对底层用到的网络API进行适配,这里根据用户传递的相关配置参数对Fetch API相关的参数和选项进行初始化。主要包括了3个主要部分,分别是:

  1. 配置Fetch API所需的各种fetchOptions,包括请求方法Get/Post、请求头、缓存策略、超时信号等。
  2. 处理请求体
    1. 如果请求体是 FormData 或 HTMLFormElement,统一转换为 FormData 对象。
    2. 如果是字符串,直接作为请求体。
  3. 处理身份验证
    1. 根据 authMode(immediate/no-prompt)获取用户凭证(credential)。
    2. 如果请求 URL 需要 API 密钥,自动添加 apiKey 到查询参数。
    3. 检查是否已有令牌(token),若无则尝试获取凭证。
2. 发送请求并处理响应

  通过 do-while 循环发送请求,直到满足终止条件:

  • 调用sendRequest(fetchOptions): 发送请求并返回响应对象 response 和解析后的数据 data
  • 处理响应状态:
  • 如果响应失败(如 HTTP 状态码非 200),抛出错误。
  • 如果响应数据包含 error 字段,标记为错误。
  • 处理重试逻辑:调用needRetry(e, r, s) 判断是否需要重试请求(例如身份验证失败后重试)。

let response, data;
try {
	do {
		[response, data] = await sendRequest(fetchOptions); // 发送请求并解析响应
	} while (!await 调用needRetry(e, r, s)); // 判断是否需要重试
} catch (n) {
// 错误处理(见下文)
}

3. sendRequest函数的内部逻辑

  sendRequest函数负责实际发送请求,流程如下:

  1. 处理代理和 URL: - 检查是否需要使用代理(s.useProxy || !!w(r))。 - 生成代理 URL(g(r))并合并查询参数。
  2. 处理请求方法:根据请求方法(如 HEADPOST)调整配置。
  3. 发送请求:使用 fetch 发送请求,支持超时控制。
  4. 解析响应数据: 1. 根据 responseType 解析为 JSON、Blob、ArrayBuffer 等。 2. 特殊处理 image 类型:通过 loadImage 函数加载图片。
// 伪代码示例
async function sendRequest(e) {
    // 处理代理和 URL
    const useProxy = e.useProxy || getProxyRule(url);
    if (useProxy) {
        url = buildProxyUrl(url);
    }

    // 发送请求
    const response = await fetch(url, fetchOptions);

    // 解析响应
    let data;
    switch (responseType) {
        case "json":
            data = await response.json();
            break;
        case "blob":
            data = await response.blob();
            break;
        // ...其他类型处理
    }

    return [response, data];
}
4. 错误处理

如果请求失败(网络错误、身份验证错误等),则会通过以下逻辑流程对错误进行统一处理:

  1. 生成错误对象: - 调用 N("request:server", error, params, response) 生成标准错误对象。 - 错误对象包含 HTTP 状态码、错误消息、原始响应数据等。
  2. 处理 SSL 重试: - 如果错误是 SSL 相关(如 403 错误码且子码为 4),启用 SSL 并重试。
  3. 拦截器处理: - 如果存在拦截器(e.interceptor),调用其 error 回调处理错误。
catch (n) {
    const t = N("request:server", n, e.params, r);
    throw t.details.ssl = e.useSSL, e.interceptor?.error?.(t), t;
}
5. 后续处理

请求成功后,处理以下逻辑:

  1. 更新信任的服务器列表: - 如果请求 URL 是 ArcGIS 的 /sharing/rest/portals/self 等路径,且响应包含 authorizedCrossOriginNoCorsDomains,注册跨域域。
  2. 处理用户凭证: - 如果凭证(e.credential)关联了服务器信息(如 owningSystemUrl),更新凭证的资源列表。

返回结果的封装


最后,请求函数透过统一的对象结构返回请求的响应信息,包含:

  • data: 解析后的响应数据(JSON、Blob 等)。
  • getAllHeaders/getHeader: 获取响应头的方法。
  • httpStatus: HTTP 状态码。
  • requestOptions: 原始请求配置。
  • ssl: 是否启用了 SSL。
  • url: 最终请求的 URL。

总体而言,ArcGIS中的网络请求封装通过一种逻辑清晰的模块化设计,实现了请求发送、代理处理、身份验证、错误处理等不同逻辑的拆分,并支持动态代理规则、身份验证重试、多种响应类型解析等灵活的高级网络请求处理,并且通过循环重试确保请求成功。在短短400行代码以内实现了常用的网络请求所需的多种特性,是一个值得我们在日常编码中学习的典范。

如果你觉得本文对你有些许启发,请持续关注我的公众号“戈伊星球”吧!