服务端都开始使用 Fetch Web API了,还不熟悉的看这里!

1,456 阅读4分钟

一、开始之前

Bun 最近发布带来最新的 v1 正式版,Bun 大而全的设计,并且贴近 Web API 标准,这里 Bun 目前支持了这些 Web API 标准,其中就包含了 fetch API, 并且 Node.js 在 Node.js v17.5.0 中实验性的增加了 fetch

二、Node.js 中的 Fetch

2.1)使用实验性 --experimental-fetch

async function getData() {
    const response = await fetch('https://github.com/');
    const body = await response.text();
    console.log(body);
}

getData()
node app.js --experimental-fetch

Node.js 的 fetch 目前还处于试验阶段,但是社区已经实现了响应的功能。

2.2)使用社区实现 node-fetch

import fetch from 'node-fetch';

const response = await fetch('https://github.com/');
const body = await response.text();

console.log(body);

三、其他服务端直接支持 Fetch API 的框架

服务支持 Fetch API 后,可方便的使用 fetch 函数,同时响应数据的方式也带来了影响,之间使用 Node HTTP repsonse.end 方法进行响应,显然直接使用 fetch apis 中的 Response 构造函数进行响应。

3.1)Bun 运行时

Bun.serve({
  fetch(req) {
    return new Response("Bun!");
  },
});

bun 对 fetch API 的支持最为直接 serve 的参数名就是 fetch, fetch 函数就是 request, 响应数据的时候使用 fetch API Response 即可。

3.2) Deno 运行时

import { serve } from "https://deno.land/std@0.140.0/http/server.ts";

async function handler(req: Request): Promise<Response> {
  const resp = await fetch("https://api.github.com/users/denoland", {
    headers: {
      accept: "application/json",
    },
  });
  return new Response(resp.body, {
    status: resp.status,
    headers: {
      "content-type": "application/json",
    },
  });
}

serve(handler);

Deno 与 Bun 类似在 serve 服务中接收 request 对象,然后响应 Response 实例,

3.3)Remix loader/action

export async function loader() {
  const users = await db.users.findMany();
  const body = JSON.stringify(users);
  return new Response(body, {
    headers: {
      "Content-Type": "application/json",
    },
  });
}

四、Web 标准 Fetch API 深度解读

4.1)fetch 相关 API设计:分块设计

api说明
fetch核心请求函数,主打 promise 风格
Request请求对象构造函数
Response响应对象构造函数
Headers请求头构造函数

4.2)与 fetch 相关的 api

api说明
AbortController取消控制器
FormData表单
URLSearchParams参数

4.3) fetch 函数的用法

4.3.1)用法一、单独传递字符串

fetch(url)

4.3.2)用法二、传递第二个参数

以对象的形式配置: 请求方式请求头请求体 等等,其中请求支持:

body 类型说明
字符串默认即可
json字符串'Content-Type': 'application/json;charset=utf-8' 头进行配合使用
表单使用FormData 对象 配合 application/x-www-form-urlencoded
文件上传使用表单提交,自动配置
二进制Blob 或 arrayBuffer 格式上传
fetch(url, {
      method: 'POST',
      headers: {
        "Content-type": "application/x-www-form-urlencoded; charset=UTF-8",
      },
      body: 'foo=bar&lorem=ipsum',
})
属性名描述
methodHTTP 请求方法(例如,'GET'、'POST')
headers包含请求头信息的对象,用于自定义请求头
body请求主体数据,可以是文本、二进制数据、表单数据等
mode请求模式,例如 'cors'、'no-cors'、'same-origin'
cache缓存模式,例如 'default'、'no-store'、'reload'
credentials请求凭证模式,例如 'same-origin'、'include'、'omit'
redirect重定向模式,例如 'follow'、'error'、'manual'
referrer请求的引用页
integrity用于验证资源完整性的哈希值
keepalive是否启用 keep-alive 功能
signal用于取消请求的信号对象,可用于中止请求

4.3.3)用法三、使用 Request 构造器与 fetch 函数

// 创建一个简单的 GET 请求
const getRequest = new Request('your_request_url', {
  method: 'GET',
  headers: new Headers({
    'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
  }),
});


// 使用 Request 对象发送请求
fetch(getRequest)
  .then(response => {
    // 处理 GET 请求的响应
  })
  .catch(error => {
    // 处理 GET 请求的错误
  });

五、示例

5.1) bun 服务端 fetch + 浏览器 fetch

bun create vite my-app
  • bun 服务端
import { serve } from 'bun';

serve({
  port: 3000,
  async fetch(req) {
    if (req.method === 'POST') {
      const bodyStream = req.body
      const _body = await Bun.readableStreamToJSON(bodyStream);
      const __body = {..._body, a: _body.a + 2}
      // 处理 POST 请求
      const body = 'Hello, POST request!' + JSON.stringify(__body);
      const headers = {'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST'}
      return new Response(body, { headers });
    } else {
      // 处理其他类型的请求
      const body = 'Hello, GET request!';
      const headers = { 'Content-Type': 'text/plain' };
      return new Response(body, { headers });
    }
  }
});

注意: 配置跨域配置, bun 中 post 请求的请求体通过 Bun.readableStreamToJSON(bodyStream) 获取。

  • react 客户端
import { useEffect, useState } from 'react'


function App() {
  const [data, setData] = useState()

  const postData = async () => {
    fetch('http://localhost:3000', {
      method: 'POST',
      mode: 'cors',
      body: JSON.stringify({ a: 1, b: 2 })
    }).then((response) => {
      return response.json()
    }).then((data) => {
      setData(data)
    })
  }

  useEffect(() => {
    postData()
  }, [])
  return (
    <>
      // ...
    </>
  )
}

export default App

使用浏览器的快鱼的时候,配置 mode: 'cors'属性。提示:也可以通过 vite 的代理配置跨域。

5.2) Remix 中使用 fetch

使用 bun 创建项目

bun create remix fetch
import type { ActionArgs, V2_MetaFunction } from "@remix-run/node";
import { Form } from '@remix-run/react'

export async function action({ request }: ActionArgs) {
  const body = await request.formData(); // 获取表单数据
  const title = body.get('title')
  const description = body.get('description')

  return new Response(JSON.stringify({title, description})) // fetch Response 相应
}

export const meta: V2_MetaFunction = () => {
  return [
    { title: "New Remix App" },
    { name: "description", content: "Welcome to Remix!" },
  ];
};

export default function Index() {
  return (
    <div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.8" }}>
      <h1>Welcome to Remix</h1>
      <Form method="post">
        <input type="text" name="title" />
        <input type="text" name="description" />
        <button type="submit">提交</button> // 默认发送 fetch 请求
      </Form>
    </div>
  );
}

Remix 中表单提交并自动发送 fetch 请求,因为前后端统一,所以不存在跨域的情况。在 Remix action 函数中获取表单提交的数据,最后使用 fetch api 中的 Response 将请求过来的数据 stringify 化,响应给表单的提交接口,可以使用 useActionData 来获取,这里就不再说明。

小结

本文核心是讲解 服务端的运行时 fetch 支持与使用情况,社区都在朝着 Web API 靠近,Deno/Bun/remix... Fetch API 虽然在前端有使用,但使用规模并不大,随着服务端靠近 Web API,使用会更加频繁,所以一个开发者对 Web API 标准掌握要更加的熟练。本文总结了 Fetch 相关的 API侧重于服务端的使用,并在不同的 JS 运行时和框架中实战。如果这篇文章能帮助到你,不妨点赞、转发来支持作者,感谢。