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个主要部分,分别是:
- 配置Fetch API所需的各种fetchOptions,包括请求方法Get/Post、请求头、缓存策略、超时信号等。
- 处理请求体
- 如果请求体是 FormData 或 HTMLFormElement,统一转换为 FormData 对象。
- 如果是字符串,直接作为请求体。
- 处理身份验证
- 根据 authMode(immediate/no-prompt)获取用户凭证(credential)。
- 如果请求 URL 需要 API 密钥,自动添加 apiKey 到查询参数。
- 检查是否已有令牌(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函数负责实际发送请求,流程如下:
- 处理代理和 URL:
- 检查是否需要使用代理(
s.useProxy || !!w(r))。 - 生成代理 URL(g(r))并合并查询参数。 - 处理请求方法:根据请求方法(如
HEAD、POST)调整配置。 - 发送请求:使用
fetch发送请求,支持超时控制。 - 解析响应数据:
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. 错误处理
如果请求失败(网络错误、身份验证错误等),则会通过以下逻辑流程对错误进行统一处理:
- 生成错误对象:
- 调用
N("request:server", error, params, response)生成标准错误对象。 - 错误对象包含 HTTP 状态码、错误消息、原始响应数据等。 - 处理 SSL 重试: - 如果错误是 SSL 相关(如 403 错误码且子码为 4),启用 SSL 并重试。
- 拦截器处理:
- 如果存在拦截器(
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. 后续处理
请求成功后,处理以下逻辑:
- 更新信任的服务器列表:
- 如果请求 URL 是 ArcGIS 的
/sharing/rest/portals/self等路径,且响应包含authorizedCrossOriginNoCorsDomains,注册跨域域。 - 处理用户凭证:
- 如果凭证(
e.credential)关联了服务器信息(如owningSystemUrl),更新凭证的资源列表。
返回结果的封装
最后,请求函数透过统一的对象结构返回请求的响应信息,包含:
data: 解析后的响应数据(JSON、Blob 等)。getAllHeaders/getHeader: 获取响应头的方法。httpStatus: HTTP 状态码。requestOptions: 原始请求配置。ssl: 是否启用了 SSL。url: 最终请求的 URL。
总体而言,ArcGIS中的网络请求封装通过一种逻辑清晰的模块化设计,实现了请求发送、代理处理、身份验证、错误处理等不同逻辑的拆分,并支持动态代理规则、身份验证重试、多种响应类型解析等灵活的高级网络请求处理,并且通过循环重试确保请求成功。在短短400行代码以内实现了常用的网络请求所需的多种特性,是一个值得我们在日常编码中学习的典范。
如果你觉得本文对你有些许启发,请持续关注我的公众号“戈伊星球”吧!