在Node.js、Deno和Bun中使用Fetch API的方法 – CodesCode
学习如何使用Fetch API - 一种简单、易用的基于Promise的替代XMLHttpRequest的方法 - 来与Node.js、Deno和Bun一起使用
在本文中,我们将讨论如何在Node.js、Deno和Bun中使用Fetch API。
- Fetch API vs XMLHttpRequest
- 一个基本的Fetch示例
- 客户端 vs 服务器端的Fetch
- 自定义Fetch请求
- 处理HTTP头部
- Fetch Promise的解析和拒绝
- 分析Fetch响应
- 中止Fetch请求
- 有效的Fetch
- 总结
Fetch API vs XMLHttpRequest
通过HTTP请求获取数据是基本的web应用程序活动。您可能已经在浏览器中进行了这样的调用,但是Fetch API在Node.js、Deno和Bun中均有原生支持。
在浏览器中,您可能会从服务器请求信息,以便在没有完全刷新屏幕的情况下显示它。这通常被称为Ajax请求或单页应用程序(SPA)。在1999年至2015年期间,XMLHttpRequest是唯一的选择,并且如果您想显示文件上传进度,它仍然是如此。XMLHttpRequest是一个相当笨重的基于回调的API,但它允许精细控制,并且尽管名称如此,它可以处理除XML之外的其他格式的响应,如文本、二进制、JSON和HTML。
自2015年起,浏览器已经实现了Fetch API。它是一个更简单、更易用、更一致的基于promise的XMLHttpRequest替代品。
您的服务器端代码也可能希望进行HTTP请求,通常是调用其他服务器上的API。从首次发布开始,Deno和Bun运行时有用地复制了浏览器的Fetch API,以便类似的代码可以在客户端和服务器上运行。Node.js直到2022年2月才需要第三方模块,如node-fetch或axios,才能使用标准的Fetch API。它仍然被视为试验性的,但现在您可以在大多数情况下在各处使用具有相同代码的fetch()。
一个基本的Fetch示例
这个简单的示例从URI中获取响应数据:
const response = await fetch('https://example.com/data.json');
fetch()调用返回一个promise,该promise会解析为提供有关结果的Response对象。您可以使用基于promise的.json()方法将HTTP响应主体解析为JavaScript对象:
const data = await response.json();// 对数据对象进行一些令人兴奋的操作// ...
客户端 vs 服务器端的Fetch
API在各平台上可能是相同的,但在进行客户端fetch()请求时,浏览器会强制执行一些限制:
-
客户端JavaScript只能与其自己域内的API端点进行通信。从
https://domainA.com/js/main.js
加载的脚本可以调用https://domainA.com/
上的任何服务,例如https://domainA.com/api/
或https://domainA.com/data/
。除非服务器通过设置HTTP Access-Control-Allow-Origin header允许访问,否则无法调用
https://domainB.com/
上的服务。 -
您的网站/应用程序可以设置
Content-Security-Policy
HTTP头部或元标签,以控制页面中允许的资源。它可以防止意外或恶意注入脚本、iframes、字体、图像、视频等。例如,设置default-src 'self'
会阻止fetch()请求数据外部自己的域(XMLHttpRequest、WebSocket、服务器推送事件和信标也受到限制)。
在Node.js、Deno和Bun中使用服务器端的Fetch API调用有较少的限制,你可以从任何服务器请求数据。也就是说,第三方API可能会:
- 需要某种身份验证或授权,使用密钥或OAuth
- 有最大请求阈值,例如每分钟不超过一次,或
- 收取商业费用以访问
你可以使用服务器端的fetch()
调用来代理客户端请求,这样可以避免CORS和CSP问题。但请记住,要做一个有责任心的网络公民,并不要用成千上万的请求轰炸服务,可能会让它们挂掉!
自定义Fetch请求
上面的示例从URI https://example.com/data.json
请求数据。在JavaScript的底层,它创建了一个Request对象,该对象表示了该请求的全部细节,例如方法、头部、正文等。
fetch()
接受两个参数:
- 资源 – 一个字符串或URL对象,以及
- 一个可选的选项参数,包含进一步的请求设置
例如:
const response = await fetch('https://example.com/data.json', { method: 'GET', credentials: 'omit', redirect: 'error', priority: 'high'});
在Node.js或客户端代码中,选项对象可以设置以下属性:
属性 | 值 |
---|---|
method |
GET (默认值),POST ,PUT ,PATCH ,DELETE 或HEAD |
headers |
一个字符串或Headers对象 |
body |
可以是字符串、JSON、blob等 |
mode |
same-origin ,no-cors 或cors |
credentials |
omit ,same-origin 或include (包括cookie和HTTP身份验证头部) |
redirect |
follow ,error 或manual (处理重定向) |
referrer |
引用URL |
integrity |
子资源完整性哈希值 |
signal |
一个AbortSignal对象,用于取消请求 |
此外,你还可以创建一个Request对象并将其传递给fetch()
。如果你可以预先定义API端点或想要发送一系列类似的请求,这可能是实用的:
const request = new Request('https://example.com/api/', { method: 'POST', body: '{"a": 1, "b": 2, "c": 3}', credentials: 'omit'});console.log(`fetching ${ request.url }`);const response = await fetch(request);
处理HTTP头部
你可以使用请求和响应中的Headers对象来操作和检查HTTP头部。如果你使用过JavaScript Maps,那么你会觉得这个API很熟悉:
// 设置初始头部const headers = new Headers({ 'Content-Type': 'text/plain',});// 添加头部headers.append('Authorization', 'Basic abc123');// 添加/更改头部headers.set('Content-Type', 'application/json');// 获取头部的值const type = headers.get('Content-Type');// 是否存在某个头部?if (headers.has('Authorization')) { // 删除某个头部 headers.delete('Authorization');}// 遍历所有头部headers.forEach((value, name) => { console.log(`${ name }: ${ value }`);});// 在fetch()中使用const response = await fetch('https://example.com/data.json', { method: 'GET', headers});// response.headers也返回一个Headers对象response.headers.forEach((value, name) => { console.log(`${ name }: ${ value }`);});
Fetch Promise Resolve和Reject
你可能会认为一个fetch()
promise在一个端点返回404 Not Found
或类似的服务器错误时会拒绝(reject)。但事实上不是这样的!在这种情况下,promise会解决(resolve),因为请求是成功的——即使结果不是你所期望的。
只有当以下情况发生时,fetch()
promise才会被拒绝:
- 你发出一个无效的请求——例如:
fetch('httttps://!invalid\URL/');
- 你取消
fetch()
请求,或者 - 发生网络错误,例如连接失败
分析 Fetch 响应
成功的fetch()
调用会返回一个包含有关状态和返回数据信息的Response对象。该对象包含以下属性:
属性 | 描述 |
---|---|
ok |
如果响应成功,则为true |
status |
HTTP状态码,例如200表示成功 |
statusText |
HTTP状态文本,例如200代码表示OK |
url |
URL |
redirected |
如果请求被重定向,则为true |
type |
响应类型:basic ,cors ,error ,opaque 或opaqueredirect |
headers |
Headers对象,表示响应的头部信息 |
body |
响应体的ReadableStream(或null) |
bodyUsed |
如果已读取响应体,则为true |
以下Response对象的方法都返回一个promise,因此应使用await
或.then
块来处理:
方法 | 描述 |
---|---|
text() |
将响应体作为字符串返回 | json() |
将响应体解析为JavaScript对象 |
arrayBuffer() |
将响应体作为一个ArrayBuffer返回 |
blob() |
将响应体作为一个Blob返回 |
formData() |
将响应体作为一个包含键/值对的FormData对象返回 |
clone() |
克隆响应,通常用于以不同方式解析响应体 |
// 示例响应const response = await fetch('https://example.com/data.json');// 这个响应返回了JSON吗?if ( response.ok && response.headers.get('Content-Type') === 'application/json') { // 解析JSON const obj = await response.json();}
取消 Fetch 请求
Node.js不会取消一个fetch()
请求;它可以永远运行下去!浏览器在1到5分钟之间等待响应。通常情况下,当你期望一个相对较快的响应时,你应该取消fetch()
。
以下示例使用了一个AbortController对象,它将一个signal
属性传递给第二个fetch()
参数。如果在五秒内fetch()
没能完成,那么一个超时就会运行.abort()
方法:
// 创建 AbortController,在 5 秒后超时
const controller = new AbortController(),
signal = controller.signal,
timeout = setTimeout(() => controller.abort(), 5000);
try {
const response = await fetch('https://example.com/slowrequest/', { signal });
clearTimeout(timeout);
console.log(response.ok);
} catch (err) {
// 超时或网络错误
console.log(err);
}
自 2022 年中以来发布的 Node.js、Deno、Bun 和大多数浏览器也支持 AbortSignal。它提供了一个更简单的 timeout() 方法,因此你不必管理自己的计时器:
try {
// 5 秒后超时
const response = await fetch('https://example.com/slowrequest/', {
signal: AbortSignal.timeout(5000),
});
console.log(response.ok);
} catch (err) {
// 超时或网络错误
console.log(err);
}
有效的 Fetch 请求
与任何基于 Promise 的异步操作一样,只有在一个调用的输入依赖于前一个调用的输出时,才应该按顺序进行 fetch()
调用。以下代码性能不佳,因为每个 API 调用都必须等待前一个调用的解决或拒绝。如果每个响应需要一秒钟,总共需要三秒钟才能完成:
// 低效的方式
const response1 = await fetch('https://example1.com/api/');
const response2 = await fetch('https://example2.com/api/');
const response3 = await fetch('https://example3.com/api/');
Promise.allSettled() 方法 并行运行多个 Promise,并在所有 Promise 都解决或拒绝后返回。这段代码的完成速度取决于最慢的响应时间。它将快三倍:
const data = await Promise.allSettled(
[
'https://example1.com/api/',
'https://example2.com/api/',
'https://example3.com/api/'
].map(url => fetch(url))
);
data
返回一个包含以下信息的对象数组:
- 每个对象具有
status
属性,值为字符串"fullfilled"
或"rejected"
- 如果解决了,
value
属性返回fetch()
的响应 - 如果拒绝了,
reason
属性返回错误信息
总结
除非你使用的是旧版本的 Node.js(17 或更低版本),否则 Fetch API 在服务器端和客户端的 JavaScript 中都可用。它灵活易用,在所有运行时环境中保持一致。只有当你需要更高级的功能,如缓存、重试或文件处理时,才需要使用第三方模块。
Leave a Reply