UNPKG

soon-fetch

Version:

a request lib alternative to axios with timeout, request reusing, race, response cache ...

1,152 lines (836 loc) 27.5 kB
[English](#soon-fetch) | [中文](#soon-fetch-1) | [Installation](#安装-installation) <!-- omit in toc --> ### soon-fetch **A lightweight http request lib , alternative to axios with timeout, request reusing, race, response cache ...** > - 🌐 automatic parse restful api url parameters > - ⭐ rapid define a request api > - ⌛ timeout disconnect > - 📦 request reusing > - 🚀 request race > - 📝 response cache > - 🔤 automatic serialization of JSON > - 📏 .min size less than **6K**, smaller after zip > - 💡 smart type tips with Typescript - [Example](#example) - [Features](#features) - [Shortcut](#shortcut) - [Restful Url Params](#restful-url-params) - [Timeout](#timeout) - [Share pending request](#share-pending-request) - [Response cache](#response-cache) - [Request race](#request-race) - [Rapid Define APIs](#rapid-define-apis) - [API](#api) ### Example > [github: soon-admin-vue3 ](https://github.com/leafio/soon-admin-vue3) > [github: soon-admin-react-nextjs ](https://github.com/leafio/soon-admin-react-nextjs) ```typescript import { createSoon } from "soon-fetch"; const soon = createSoon( (url, options) => { const isGet = !options?.method || options?.method.toLocaleLowerCase() === "get" return { baseURL: '/api', baseOptions: { timeout: 20 * 1000, headers: new Headers({ Authorization: "Bearer " + localStorage.getItem("token"), }), share: isGet ? true : false, staleTime: isGet ? 2 * 1000 : 0, }, } }, ({ parsed }) => { return <T>() => { return fetch(parsed.url, parsed.options).then((res) => res.json() ) as Promise<T>; }; } ); /** GET */ soon.get("/user?id=123"); soon.get("/user", { query: { id: 123 } }); soon.get("/user/:id", { params: { id: 123 } }); /** POST */ soon.post("/login", { body: { username: "admin", password: "123456" } }); /**Define API */ export const login = soon .POST("/user/login") .Body<{ username: string; password: string }>() .Send<{ token: string }>(); //the develop tools will have type tips for request and response login({ username: "admin", password: "123" }).then((res) => { localStorage.setItem("token", res.token); }); ``` ### Features ##### Shortcut ```typescript soon.get(url, options); soon.post(url, options); soon.put(url, options); soon.patch(url, options); soon.delete(url, options); soon.head(url, options); soon.options(url, options); ``` ##### Restful Url Params url like /:key , will handle the key ```typescript soon.get("/api/user/:id", { params: { id: 1 } }); // api/user/1 soon.get("/api/:job/:year", { params: { job: "engineer", year: 5 } }); //api/engineer/5 ``` ##### Timeout ```typescript //** the request level timeout, will override the instance level timeout */ soon.get(url, { timeout: 1000 * 20 }); ``` ##### Share pending request If a request is made again before the first completes, will reuse the first request instead of making a new request. ```ts soon.get(url, { share: true }); ``` ##### Response cache A cached response will be returned if the request is made again within the specified time. ```ts soon.get(url, { staleTime: 1000 * 60 * 5 }); ``` ##### Request race ‌If a second request is made before the first completes, abort the first to avoid race conditions from out-of-order responses. ```tsx import { useEffect, useRef, useState } from "react"; type User = { name: string; job: string }; const api = soon.GET("/api/users").Query<{ page: number }>().Send<User[]>(); export default function App() { const refAbort = useRef([]); const [list, setList] = useState<User[]>([]); const [page, setPage] = useState(1); useEffect(() => { api({ page }, { aborts: refAbort.current }) .then(setList) .catch(console.log); }, [page]); return ( <div> <button onClick={() => setPage((pre) => pre + 1)}>next</button> <div> {list.map((item) => ( <div key={item.name}>{item.name}</div> ))} </div> </div> ); } ``` ##### Rapid Define APIs ```typescript //can be GET POST PATCH PUT DELETE soon.GET(url:string).Query<Query>().Send<Response>() soon.POST(url:string).Body<Body>().Send<Response>() //define an api export const getUserInfo = soon.GET("/user/:id").Send(); //then use in any where getUserInfo({ id: 2 }).then((res) => console.log(res)); //with typescript, export const login = soon .POST("/user/login") .Body<{ username: string; password: string }>() .Send<{ token: string }>(); //the develop tools will have type tips for request and response login({ username: "admin", password: "123" }).then((res) => { localStorage.setItem("token", res.token); }); ``` ### API #### SoonOptions ```ts // function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> // RequestInit is fetch's init options type SoonOptions = Omit<RequestInit, "body"> & { body?: RequestInit["body"] | object; query?: | Record< string, | string | number | boolean | null | undefined | (string | number | boolean | null | undefined)[] > | URLSearchParams; params?: Record<string, string | number>; timeout?: number; aborts?: AbortController[] | never[]; share?: boolean; staleTime?: number; }; ``` #### createSoon Create a soon request instance. **Parameters:** - `getConfig`: A function to get request configuration, receives url and options, returns an object containing url, options, baseURL, and baseOptions - `wrapper`: A wrapper function to handle request logic **Returns:** An object containing request method and various shortcut methods **Example:** ```javascript const soon = createSoon( (url, options) => ({ url, options,baseURL:'/api', baseOptions:{timeout: 5000} }), ({ parsed }) => async (url, options) => { const response = await fetch(parsed.url, parsed.options); return response.json(); } ); // Usage example const data = await soon.get("/api/users"); ``` #### createShortApi Factory function to create API shortcut methods. Used to generate type-safe API calling methods, supporting path parameters, query parameters, and request body. **Parameters:** - `wrapper`: Wrapper function to handle actual request logic **Returns:** An object containing GET, POST, PUT, DELETE, PATCH and other methods, each method supports chain calling **Example:** ```javascript const API = createShortApi( async (url, method, params, query, body, options) => { // Handle request logic const _url = mergeUrl(url, { params, query }); const response = await fetch(_url, { ...options, method, body }); return response.json(); } ); // Usage example const getUser = API.GET("/api/users/:id").Send(); const userData = await getUser({ id: 1 }); ``` #### createShortMethods Factory function to create shortcut methods. **Parameters:** - `methods`: HTTP methods array - `wrapper`: Wrapper function that receives method name and returns a function to process requests **Returns:** Shortcut call object containing specified methods **Example:** ```javascript const methods = createShortMethods(["get", "post"], (method) => { return (url, options) => fetch(url, { ...options, method }); }); // Usage: methods.get('/api/users') ``` #### parseUrlOptions Parse URL options. **Parameters:** - `urlOptions`: Object containing url, options, baseURL and baseOptions **Returns:** Tuple of processed url and options **Example:** ```javascript const [url, options] = parseUrlOptions({ url: "/api/users/:id", options: { params: { id: "123" } }, baseURL: "https://api.example.com", }); // Returns: ['https://api.example.com/api/users/123', options] ``` #### mergeHeaders Merge multiple Headers objects. **Parameters:** - `headersList`: List of Headers objects to merge **Returns:** Merged Headers object, later ones will overwrite earlier ones with the same name **Example:** ```javascript const headers1 = { "Content-Type": "application/json" }; const headers2 = { Authorization: "Bearer token" }; const mergedHeaders = mergeHeaders(headers1, headers2); ``` #### mergeSignals Merge multiple AbortSignal signals. **Parameters:** - `signals`: Array of AbortSignals to merge - `timeout`: Optional timeout time (milliseconds) **Returns:** Merged AbortSignal, any signal termination will trigger termination **Example:** ```javascript const controller1 = new AbortController(); const controller2 = new AbortController(); const mergedSignal = mergeSignals( [controller1.signal, controller2.signal], 5000 ); ``` #### mergeUrl Merge URL and its related parameters. Handle baseURL, path parameters and query parameters to generate complete URL. **Parameters:** - `url`: Original URL - `config`: Configuration object, including query parameters, path parameters and base URL **Returns:** Processed complete URL **Example:** ```javascript const url = mergeUrl("/api/users/:id", { params: { id: "123" }, query: { filter: "active" }, baseURL: "https://api.example.com", }); // Returns: 'https://api.example.com/api/users/123?filter=active' ``` #### mergeOptions Merge multiple option objects. Merge request options, including special handling of headers and signals. **Parameters:** - `optionsList`: List of option objects to merge **Returns:** Merged option object **Example:** ```javascript const defaultOptions = { timeout: 5000, headers: { "Content-Type": "application/json" }, }; const requestOptions = { method: "POST", body: JSON.stringify({ name: "John" }), }; const mergedOptions = mergeOptions(defaultOptions, requestOptions); ``` #### isBodyJson Determine if the request body is a JSON object. Check if body is a plain object, not special types like FormData or Blob. **Parameters:** - `body`: Request body **Returns:** Returns true if it is a JSON object, otherwise false **Example:** ```javascript isBodyJson({ name: "John" }); // true isBodyJson(new FormData()); // false isBodyJson("string"); // false ``` #### genRequestKey Generate a unique identification key for the request. Generate a unique key value based on the request's URL, method, headers, body, query parameters, etc., used for caching and request sharing. **Parameters:** - `req`: Request object containing url and options **Returns:** Unique identification string of the request **Example:** ```javascript const key = genRequestKey({ url: "/api/users", options: { method: "GET", params: { id: 1 } }, }); ``` #### raceAbort Race condition handling function. Used to handle request race conditions, terminating previous requests. **Parameters:** - `abortController`: Controller of the current request - `controllers`: Array of existing controllers **Example:** ```javascript const controller = new AbortController(); const controllers = []; raceAbort(controller, controllers); // Terminate previous requests and add current controller ``` #### createCache Create cache instance. Provides request result caching function, supports expiration time. **Returns:** Cache object containing get, set, remove methods **Example:** ```javascript const cache = createCache(); cache.set("key", { data: "value" }, Date.now() + 60000); // Cache for 1 minute const data = cache.get("key"); cache.remove("key"); ``` #### deepSort Deep sort object keys. Recursively sort the keys of an object to generate a stable object serialization result. **Parameters:** - `obj`: Object to sort **Returns:** Object with sorted keys **Example:** ```javascript const obj = { b: 2, a: 1, c: { z: 3, y: 2 } }; const sorted = deepSort(obj); // Returns: { a: 1, b: 2, c: { y: 2, z: 3 } } ``` #### createShare Create request sharing instance. Used to share the same request to avoid duplicate requests. **Returns:** Sharing object containing get and set methods **Example:** ```javascript const share = createShare(); const promise = fetch("/api/data"); share.set("key", promise); const sharedPromise = share.get("key"); // Get the same promise ``` #### createSilentRefresh Create silent refresh instance. Used to handle silent refresh functionality when token expires. **Parameters:** - `refresh_token_fn`: Function to refresh token **Returns:** Function that accepts success and failure callbacks **Example:** ```javascript const silentRefresh = createSilentRefresh(async () => { // Refresh token logic await refreshToken(); }); // Usage example silentRefresh( () => console.log("Refresh successful"), () => console.log("Refresh failed") ); ``` #### parseWithBase Parse base URL configuration. Process baseURL, headers, body and other configuration items to generate final request configuration. **Parameters:** - `urlOptions`: Object containing url, options, baseURL and baseOptions **Returns:** Processed url, options, is_body_json and abortController **Example:** ```javascript const result = parseWithBase({ url: "/api/users", options: { method: "GET" }, baseURL: "https://api.example.com", }); ``` #### toFormData Convert object to FormData. Used to convert plain objects to FormData format, supports files and regular values. **Parameters:** - `body`: Object to convert **Returns:** Converted FormData object **Example:** ```javascript const formData = toFormData({ name: "John", avatar: fileBlob, age: 30, }); ``` [English](#soon-fetch) | [中文](#soon-fetch-1) | [Installation](#安装-installation) <!-- omit in toc --> #### soon-fetch **极轻量的请求库,不到 6K** > - 🌐 自动解析 rest Url 的参数 > - ⭐ 快捷定义请求 api > - ⌛ 超时断开 > - 📦 请求复用 > - 🚀 请求竞态 > - 📝 响应缓存 > - 🔤 自动处理 JSON > - 📏 不到 **6K** , zip 后会更小 > - 💡 用 typescript 有智能类型提醒 - [示例](#示例) - [特别功能](#特别功能) - [快捷方法](#快捷方法) - [Restful Url 参数自动处理](#restful-url-参数自动处理) - [共享未完成的请求](#共享未完成的请求) - [超时](#超时) - [响应缓存](#响应缓存) - [请求竞态](#请求竞态) - [快速定义 API](#快速定义-api) - [API](#api-1) ### 示例 > [github: soon-admin-vue3 ](https://github.com/leafio/soon-admin-vue3) > [github: soon-admin-react-nextjs ](https://github.com/leafio/soon-admin-react-nextjs) ```typescript const soon = createSoon( (url, options) => { const isGet = !options?.method || options?.method.toLocaleLowerCase() === "get" return { baseURL: '/api', baseOptions: { timeout: 20 * 1000, headers: new Headers({ Authorization: "Bearer " + localStorage.getItem("token"), }), share: isGet ? true : false, staleTime: isGet ? 2 * 1000 : 0, }, } }, ({ parsed }) => { return <T>() => { return fetch(parsed.url, parsed.options).then((res) => res.json() ) as Promise<T>; }; } ); /** GET */ soon.get("/user?id=123"); soon.get("/user", { query: { id: 123 } }); soon.get("/user/:id", { params: { id: 123 } }); /** POST */ soon.post("/login", { body: { username: "admin", password: "123456" } }); /**定义 API */ export const login = soon .POST("/user/login") .Body<{ username: string; password: string }>() .Send<{ token: string }>(); //开发工具会有请求和响应的智能提醒 login({ username: "admin", password: "123" }).then((res) => { localStorage.setItem("token", res.token); }); ``` ### 特别功能 ##### 快捷方法 ```typescript soon.get(url, options); soon.post(url, options); soon.put(url, options); soon.patch(url, options); soon.delete(url, options); soon.head(url, options); soon.options(url, options); ``` ###### Restful Url 参数自动处理 url 包含 /:key 会解析匹配 key ```typescript soon.get("/api/user/:id", { params: { id: 1 } }); // api/user/1 soon.get("/api/:job/:year", { params: { job: "engineer", year: 5 } }); //api/engineer/5 ``` ##### 超时 ```typescript //** 请求级超时, 会覆盖实例级超时 */ soon.get(url, { timeout: 1000 * 20 }); ``` ##### 共享未完成的请求 如果在第一个请求完成之前再次发起相同的请求,则会复用第一个请求,而不是发起新的请求。 ```ts soon.get(url, { share: true }); ``` ##### 响应缓存 如果在指定时间内再次发起相同的请求,则会返回缓存的响应。 ```ts soon.get(url, { staleTime: 1000 * 60 * 5 }); ``` ##### 请求竞态 如果在第一个请求完成之前发起第二个请求,则会中止第一个请求,以避免因响应顺序错乱导致的问题。 ```tsx import { useEffect, useRef, useState } from "react"; type User = { name: string; job: string }; const api = soon.GET("/api/users").Query<{ page: number }>().Send<User[]>(); export default function App() { const refAbort = useRef([]); const [list, setList] = useState<User[]>([]); const [page, setPage] = useState(1); useEffect(() => { api({ page }, { aborts: refAbort.current }) .then(setList) .catch(console.log); }, [page]); return ( <div> <button onClick={() => setPage((pre) => pre + 1)}>next</button> <div> {list.map((item) => ( <div key={item.name}>{item.name}</div> ))} </div> </div> ); } ``` ##### 快速定义 API ```ts //可以是 GET POST PATCH PUT DELETE //GET 请求数据传递至query,其他方法请求数据传递至body soon.GET(url:string).Query<Query>().Send<Response>() soon.POST(url:string).Body<Body>().Send<Response>() //定义一个api export const getUserInfo=soon.GET('/user/:id').Send() //使用 getUserInfo({id:2}).then(res=>console.log(res)) //用typescript, export const login=soon .POST('/user/login') .Body<{username:string,password:string}>() .Send<{token:string}>() //开发工具会有请求和响应的智能提醒 login({username:'admin',password:'123'}).then(res=>{ localStorage.setItem('token', res.token); }) ``` ### API #### SoonOptions ```ts // function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> // RequestInit 为原生 fetch 的 init 选项 type SoonOptions = Omit<RequestInit, "body"> & { body?: RequestInit["body"] | object; query?: | Record< string, | string | number | boolean | null | undefined | (string | number | boolean | null | undefined)[] > | URLSearchParams; params?: Record<string, string | number>; timeout?: number; aborts?: AbortController[] | never[]; share?: boolean; staleTime?: number; }; ``` #### createSoon 创建一个 soon 请求实例。 **参数:** - `getConfig`: 用于获取请求配置的函数,接收 url 和 options,返回包含 url、options、baseURL 和 baseOptions 的对象 - `wrapper`: 包装器函数,用于处理请求逻辑 **返回:** 包含 request 方法和各种快捷方法的对象 **示例:** ```javascript const soon = createSoon( (url, options) => ({ url, options,baseURL:'/api', baseOptions:{timeout: 5000} }), ({ parsed }) => async (url, options) => { const response = await fetch(parsed.url, parsed.options); return response.json(); } ); // 使用示例 const data = await soon.get("/api/users"); ``` #### createShortApi 创建 API 快捷方法的工厂函数。 用于生成类型安全的 API 调用方法,支持路径参数、查询参数和请求体。 **参数:** - `wrapper`: 包装函数,用于处理实际的请求逻辑 **返回:** 包含 GET、POST、PUT、DELETE、PATCH 等方法的对象,每个方法都支持链式调用 **示例:** ```javascript const API = createShortApi( async (url, method, params, query, body, options) => { // 处理请求逻辑 const _url = mergeUrl(url, { params, query }); const response = await fetch(_url, { ...options, method, body }); return response.json(); } ); // 使用示例 const getUser = API.GET("/api/users/:id").Send(); const userData = await getUser({ id: 1 }); ``` #### createShortMethods 创建快捷方法的工厂函数。 **参数:** - `methods`: HTTP 方法数组 - `wrapper`: 包装函数,接收方法名,返回处理请求的函数 **返回:** 包含指定方法的快捷调用对象 **示例:** ```javascript const methods = createShortMethods(["get", "post"], (method) => { return (url, options) => fetch(url, { ...options, method }); }); // 使用: methods.get('/api/users') ``` #### parseUrlOptions 解析 URL 选项。 **参数:** - `urlOptions`: 包含 url、options、baseURL 和 baseOptions 的对象 **返回:** 处理后的 url 和 options 元组 **示例:** ```javascript const [url, options] = parseUrlOptions({ url: "/api/users/:id", options: { params: { id: "123" } }, baseURL: "https://api.example.com", }); // 返回: ['https://api.example.com/api/users/123', options] ``` #### mergeHeaders 合并多个 Headers 对象。 **参数:** - `headersList`: 要合并的 Headers 对象列表 **返回:** 合并后的 Headers 对象,后面的会覆盖前面的同名 header **示例:** ```javascript const headers1 = { "Content-Type": "application/json" }; const headers2 = { Authorization: "Bearer token" }; const mergedHeaders = mergeHeaders(headers1, headers2); ``` #### mergeSignals 合并多个 AbortSignal 信号。 **参数:** - `signals`: 要合并的 AbortSignal 数组 - `timeout`: 可选的超时时间(毫秒) **返回:** 合并后的 AbortSignal,任意一个信号终止都会触发终止 **示例:** ```javascript const controller1 = new AbortController(); const controller2 = new AbortController(); const mergedSignal = mergeSignals( [controller1.signal, controller2.signal], 5000 ); ``` #### mergeUrl 合并 URL 及其相关参数。 处理 baseURL、路径参数和查询参数,生成完整 URL。 **参数:** - `url`: 原始 URL - `config`: 配置对象,包含查询参数、路径参数和基础 URL **返回:** 处理后的完整 URL **示例:** ```javascript const url = mergeUrl("/api/users/:id", { params: { id: "123" }, query: { filter: "active" }, baseURL: "https://api.example.com", }); // 返回: 'https://api.example.com/api/users/123?filter=active' ``` #### mergeOptions 合并多个选项对象。 合并请求选项,包括 headers 和 signal 等特殊处理。 **参数:** - `optionsList`: 要合并的选项对象列表 **返回:** 合并后的选项对象 **示例:** ```javascript const defaultOptions = { timeout: 5000, headers: { "Content-Type": "application/json" }, }; const requestOptions = { method: "POST", body: JSON.stringify({ name: "John" }), }; const mergedOptions = mergeOptions(defaultOptions, requestOptions); ``` #### isBodyJson 判断请求体是否为 JSON 对象。 检查 body 是否为普通对象,而不是 FormData、Blob 等特殊类型。 **参数:** - `body`: 请求体 **返回:** 如果是 JSON 对象返回 true,否则返回 false **示例:** ```javascript isBodyJson({ name: "John" }); // true isBodyJson(new FormData()); // false isBodyJson("string"); // false ``` #### genRequestKey 生成请求的唯一标识键。 根据请求的 URL、方法、headers、body、查询参数等生成唯一键值,用于缓存和请求共享。 **参数:** - `req`: 包含 url 和 options 的请求对象 **返回:** 请求的唯一标识字符串 **示例:** ```javascript const key = genRequestKey({ url: "/api/users", options: { method: "GET", params: { id: 1 } }, }); ``` #### raceAbort 竞态处理函数。 用于处理请求竞态,终止之前的请求。 **参数:** - `abortController`: 当前请求的控制器 - `controllers`: 已存在的控制器数组 **示例:** ```javascript const controller = new AbortController(); const controllers = []; raceAbort(controller, controllers); // 终止之前的请求并添加当前控制器 ``` #### createCache 创建缓存实例。 提供请求结果缓存功能,支持过期时间。 **返回:** 包含 get、set、remove 方法的缓存对象 **示例:** ```javascript const cache = createCache(); cache.set("key", { data: "value" }, Date.now() + 60000); // 缓存1分钟 const data = cache.get("key"); cache.remove("key"); ``` #### deepSort 深度排序对象键。 递归地对对象的键进行排序,用于生成稳定的对象序列化结果。 **参数:** - `obj`: 要排序的对象 **返回:** 键排序后的对象 **示例:** ```javascript const obj = { b: 2, a: 1, c: { z: 3, y: 2 } }; const sorted = deepSort(obj); // 返回: { a: 1, b: 2, c: { y: 2, z: 3 } } ``` #### createShare 创建请求共享实例。 用于共享相同请求,避免重复请求。 **返回:** 包含 get 和 set 方法的共享对象 **示例:** ```javascript const share = createShare(); const promise = fetch("/api/data"); share.set("key", promise); const sharedPromise = share.get("key"); // 获取相同 promise ``` #### createSilentRefresh 创建静默刷新实例。 用于处理 token 过期时的静默刷新功能。 **参数:** - `refresh_token_fn`: 刷新 token 的函数 **返回:** 接收成功和失败回调的函数 **示例:** ```javascript const silentRefresh = createSilentRefresh(async () => { // 刷新token逻辑 await refreshToken(); }); // 使用示例 silentRefresh( () => console.log("刷新成功"), () => console.log("刷新失败") ); ``` #### parseWithBase 解析基础 URL 配置。 处理 baseURL、headers、body 等配置项,生成最终的请求配置。 **参数:** - `urlOptions`: 包含 url、options、baseURL 和 baseOptions 的对象 **返回:** 处理后的 url、options、is_body_json 和 abortController **示例:** ```javascript const result = parseWithBase({ url: "/api/users", options: { method: "GET" }, baseURL: "https://api.example.com", }); ``` #### toFormData 将对象转换为 FormData。 用于将普通对象转换为 FormData 格式,支持文件和普通值。 **参数:** - `body`: 要转换的对象 **返回:** 转换后的 FormData 对象 **示例:** ```javascript const formData = toFormData({ name: "John", avatar: fileBlob, age: 30, }); ``` [English](#soon-fetch) | [中文](#soon-fetch-1) | [Installation](#安装-installation) <!-- omit in toc --> ##### 安装 Installation ```bash npm install soon-fetch ```