译者 | Craig Buckler
译者者 | Phoenix
策画 | 闫短蕊
责任编辑起初正式发布于 OpenReplay 博客,由 InfoQ 英文站译者并撷取。
Ajax 是绝大多数 web 插件另一面的核心控制技术,它容许网页向 web 服务项目收到触发器允诺,因此统计数据可以不历经网页来往伺服器无刷新显示统计数据。
名词 Ajax 不是一类控制技术,恰好相反,它指的从应用领域程序JAVA读取伺服器统计数据的方式。多年来早已导入了三种优先选择,目前有三种主要方式,绝大多数 JavaScript 架构采用其中一类或三种。
在责任编辑中,他们将科学研究晚期 XMLHttpRequest 和当代 Fetch 的优劣,以确认何种 Ajax API 最适宜你的应用。
XMLHttpRequest
XMLHttpRequest 在 1999 年首度作为冗余的 Internet Explorer 5.0 ActiveX 模块出现,谷歌合作开发它是为的是全力支持如前所述应用领域程序的 Outlook 版,XML 是彼时最盛行(或被鼓吹)的统计数据库系统,除此以外,XMLHttpRequest 还全力支持文档和仍未发明者的 JSON。
Jesse James Garrett 在他 2005 年的该文《AJAX: Web 插件的新方式》中明确提出了“AJAX”基本概念,从那时起Google电子邮箱和Google世界地图等如前所述 AJAX 的插件早已存有,但是这个名词鞭策了合作开发者,并引发了简洁的 Web 2.0 新体验爆发式增长。
AJAX 是“Asynchronous JavaScript and XML”的简写,虽然严苛蔡伯介,合作开发者无须采用触发器方式、JavaScript 或 XML。他们现在将通用型的“Ajax”术
大部份非主流应用领域程序都全力支持 XMLHttpRequest,并在 2006 年成为非官方的 web 国际标准。上面是一个单纯的范例,从你的域
const xhr = new XMLHttpRequest();
xhr.open(“GET”, “/service”);
// state change event
xhr.onreadystatechange = () => {
// is request complete?
if (xhr.readyState !== 4) return;
if (xhr.status === 200) {
// request successful
console.log(JSON.parse(xhr.responseText));
} else {
// request not successful
console.log(“HTTP error”, xhr.status, xhr.statusText);
}
};
// start request
xhr.send();
onreadystatechange 回调函数在允诺的生命周期中运行好几次;XMLHttpRequest 对象的 readyState 属性则返回当前状态:
0 (uninitialized) – 允诺未初始化
1(loading)- 伺服器连接建立
2(loaded)- 允诺收到
3(interactive)- 处理允诺
4(complete)- 允诺完成,响应准备就绪
在达到状态 4 之前,几个函数就可以做很多事情。
Fetch
Fetch 是一个当代如前所述 promise 的 Ajax 允诺 API,首度出现于 2015 年,在绝大多数应用领域程序中都得到了全力支持。它不是如前所述 XMLHttpRequest 构建的,并且用更简洁的语法提供了更好的一致性。上面的 Promise 链函数与上面的 XMLHttpRequest 范例相同:
fetch(“/service”, { method: “GET” })
.then((res) => res.json())
.then((json) => console.log(json))
.catch((err) => console.error(“error:”, err));
或者你可以采用 async/await:
try {
const res = await fetch(“/service”, { method: “GET” }),
json = await res.json();
console.log(json);
} catch (err) {
console.error(“error:”, err);
}
Fetch 更清晰、更简洁,并且经常在 Service worker 中采用。
开源会话重播
OpenReplay 是 FullStory 和 LogRocket 的开源替代品,它通过回放用户在你的插件上的一切操作,并显示每个问题的操作堆栈,提供完整的可观察性。OpenReplay 是自托管的,可以完全控制你的统计数据。
快乐调试吧!当代的前端团队 —— 开始自由地监控你的 web 插件。
第 1 回合:Fetch 获胜
与陈旧的 XMLHttpRequest 相比,Fetch API 除了具有更清晰简洁的语法之外,还有其它几个优势。
头、允诺和响应对象
上面单纯 fetch() 示例中,采用一个字符串定义 URL 端点,也可以传递一个可配置的 Request 对象,它提供了有关调用的一系列属性:
const request = new Request(“/service”, { method: “POST” });
console.log(request.url);
console.log(request.method);
console.log(request.credentials);
// FormData representation of body
const fd = await request.formData();
// clone request
const req2 = request.clone();
const res = await fetch(request);
Response 对象提供了对访问大部份详细信息的类似访问:
console.log(res.ok); // true/false
console.log(res.status); // HTTP status
console.log(res.url);
const json = await res.json(); // parses body as JSON
const text = await res.text(); // parses body as text
const fd = await res.formData(); // FormData representation of body
// set request headers
const headers = new Headers();
headers.set(“X-Requested-With”, “ajax”);
headers.append(“Content-Type”, “text/xml”);
const request = new Request(“/service”, {
method: “POST”,
headers,
});
const res = await fetch(request);
// examine response headers
console.log(res.headers.get(“Content-Type”));
缓存控制
在 XMLHttpRequest 中管理缓存具有挑战性,你可能会发现有必要附加一个随机查询字符串值来绕过应用领域程序缓存,Fetch 方式在第二个参数 init 对象中内置了对缓存的全力支持:
const res = await fetch(“/service”, {
method: “GET”,
cache: “default”,
});
缓存可以设置为:
default —— 如果有一个新的 (未过期的) 匹配,则采用应用领域程序缓存;如果没有,应用领域程序会收到一个带条件的允诺来检查资源是否已改变,并在必要时会收到新的允诺
no-store —— 绕过应用领域程序缓存,并且网络响应不会更新它
reload —— 绕过应用领域程序缓存,但是网络响应会更新它
no-cache —— 类似于default,除了一个条件允诺总是被做
force-cache —— 如果可能,采用缓存的版,即使它过时了
only-if-cached —— 相同的 force-cache,除了没有网络允诺
跨域控制
跨域共享资源允许应用领域程序JAVA向另一个域收到 Ajax 允诺,前提是该伺服器容许 Access-Control-Allow-Origin 响应头中的源域;如果没有设置这个参数, fetch() 和 XMLHttpRequest 都会失败。但是,Fetch 提供了一个模式属性,可以在第二个参数的 init 对象中设置‘no-cors’属性。
const res = await fetch(
https://anotherdomain.com/service,
{
method: GET,
mode: no-cors
}
);
这将返回一个不能读取但可以被其它的 API 采用的响应。例如,你可以采用 Cache API 存储返回再之后采用,可能从 Service Worker 返回一个图像、JAVA或 CSS 文件。
凭证控制
XMLHttpRequest 总是发送应用领域程序 cookie,Fetch API 不会发送 cookie,除非你显式地在第二个参数 init 对象中设置 credentials 属性。
const res = await fetch(“/service”, {
method: “GET”,
credentials: “same-origin”,
});
credentials 可以设置为:
omit —— 排除 cookie 和 HTTP 认证项 (默认)
same-origin —— 包含对同源 url 的允诺的凭证
include —— 包含大部份允诺的凭证
请注意,include 是晚期 API 实现中的默认值,如果你的用户可能运行旧的应用领域程序,就得显式地设置 credentials 属性。
重定向控制
默认情况下,fetch() 和 XMLHttpRequest 都遵循伺服器重定向。但是,fetch() 在第二个参数 init 对象中提供了替代选项:
const res = await fetch(“/service”, {
method: “GET”,
redirect: “follow”,
});
redirect 可以设置为:
follow —— 遵循大部份重定向(默认)
error —— 发生重定向时中止(拒绝)
manual —— 返回手动处理的响应
统计数据流
XMLHttpRequest 将整个响应读入内存缓冲区,但是 fetch() 可以流式传输允诺和响应统计数据,这是一项新控制技术,流容许你在发送或接收时处理更小的数据块。例如,你可以在完全下载前处理数兆字节文件中的信息,上面的示例将传入的(二进制)统计数据块转换为文档,并将其输出到控制台。在较慢的连接上,你会看到更小的统计数据块在较长的时间内到达。
const response = await fetch(“/service”),
reader = response.body
.pipeThrough(new TextDecoderStream())
.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) break;
console.log(value);
}
伺服器端全力支持
Deno 和 Node 18 中完全全力支持 Fetch,在伺服器和应用领域程序采用相同的 API 有助于减少认知成本,还提供了在任何地方运行的同构 JavaScript 库的可能性。
第二轮:XMLHttpRequest 获胜
虽然存有缺陷,XMLHttpRequest 还是有一些技巧可以超越 ajax Fetch()。
进度全力支持
他们可以监控允诺的进度,通过将一个处理程序附加到 XMLHttpRequest 对象的进度事件上。这在上传大文件(如照片)时特别有用:
const xhr = new XMLHttpRequest();
// progress event
xhr.upload.onprogress = (p) => {
console.log(Math.round((p.loaded / p.total) * 100) + “%”);
};
事件处理程序传递的对象有三个属性:
lengthComputable —— 如果进度可以计算,则设置为 true
total —— 消息体的工作总量或内容长度
loaded —— 到目前为止完成的工作或内容的数量
Fetch API 没有提供任何方式来监控上传进度。
超时全力支持
XMLHttpRequest 对象提供了一个 timeout 属性,可以将其设置为允诺自动终止前容许运行的毫秒数;如果超时,就触发一个 timeout 事件来处理:
const xhr = new XMLHttpRequest();
xhr.timeout = 5000; // 5-second maximum
xhr.ontimeout = () => console.log(“timeout”);
fetch() 中可以封装一个函数来实现超时功能:
function fetchTimeout(url, init, timeout = 5000) {
return new Promise((resolve, reject) => {
fetch(url, init).then(resolve).catch(reject);
setTimeout(reject, timeout);
});
}
或者,你可以采用 Promise.race():
Promise.race([
fetch(“/service”, { method: “GET” }),
new Promise((resolve) => setTimeout(resolve, 5000)),
]).then((res) => console.log(res));
这两个方式都不容易采用,另外允诺将在后台继续运行。
中止全力支持
运行中的允诺可以通过 XMLHttpRequest 的 abort() 方式取消,如有必要,可以附加一个 abort 事件来处理:
const xhr = new XMLHttpRequest();
xhr.open(“GET”, “/service”);
xhr.send();
// …
xhr.onabort = () => console.log(“aborted”);
xhr.abort();
你可以中止一个 fetch(),但它不是那么直接,需要一个 AbortController 对象:
const controller = new AbortController();
fetch(“/service”, {
method: “GET”,
signal: controller.signal,
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch((error) => console.error(“Error:”, error));
// abort request
controller.abort();
当 fetch() 中止时,catch() 块执行。
更显式的故障检测
当合作开发者第一次采用 fetch() 时,假设一个 HTTP 错误,如 404 Not Found 或 500 Internal Server error 将触发 Promise 拒绝并运行相关的 catch() 块,这似乎是合乎逻辑的,但事实并非如此:Promise 成功地解决了这些响应,只有当网络没有响应或允诺被中断时,才会发生拒绝。
fetch() 的 Response 对象提供了 status 和 ok 属性,但并不总是显式地需要检查它们,XMLHttpRequest 更明确,因为单个回调函数处理每一个结果:你应该在每个示例中都看到 stuatus 检查。
浏览器全力支持
我希望你不必全力支持 Internet Explorer 或 2015 年之前的应用领域程序版,但如果是这样的话,XMLHttpRequest 是你唯一的优先选择。XMLHttpRequest 也很稳定的,API 不太可能更新。Fetch 比较新,还缺少几个关键特性,虽然更新不太可能破坏代码,但你可以期待一些维护。
应该采用哪个 API ?
绝大多数合作开发者都会采用更新的 Fetch API,它的语法更简洁,比 XMLHttpRequest 更有优势;也就是说,这些好处中的许多都有特定的用例,但在绝大多数插件中都不需要它们。只有三种情况下 XMLHttpRequest 仍必不可少:
你正在全力支持非常老的浏览器——这种需求会随着时间的推移而下降。
你需要显示上传进度条。Fetch 后续将会全力支持,但可能需要几年的时间。
这三种优先选择都很有趣,值得详细了解它们!
原文链接:
https://blog.openreplay.com/ajax-battle-xmlhttprequest-vs-the-fetch-api