在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

通过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()请求时,浏览器会强制执行一些限制:

  • 跨源资源共享(CORS)

    客户端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/上的服务。

  • 内容安全策略(CSP)

    您的网站/应用程序可以设置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(默认值),POSTPUTPATCHDELETEHEAD
headers 一个字符串或Headers对象
body 可以是字符串、JSON、blob等
mode same-originno-corscors
credentials omitsame-origininclude(包括cookie和HTTP身份验证头部)
redirect followerrormanual(处理重定向)
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 响应类型:basiccorserroropaqueopaqueredirect
headers Headers对象,表示响应的头部信息
body 响应体的ReadableStream(或null)
bodyUsed 如果已读取响应体,则为true

以下Response对象的方法都返回一个promise,因此应使用await.then块来处理:

tr>

方法 描述
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

Your email address will not be published. Required fields are marked *