npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

dart-request

v0.0.1

Published

DART.IM组件库 - 基于原生 fetch 的通用请求库

Readme

dart-request

基于原生 fetch 的通用请求库。axios 风格实例、拦截器、超时与取消、重试、重复请求去重、下载进度;运行时除 dart-common 外零第三方依赖,浏览器与 Node 18+ 同构。

特性

  • 原生实现:只用 fetch / AbortController / Headers / FormData / Blob / ReadableStream
  • axios 风格实例:createRequest() 得到独立配置、独立拦截器、独立去重表
  • 可控失败:统一 RequestError 与稳定错误码,HTTP / 网络 / 超时 / 取消 / 解析可区分
  • 纯 HTTP 层:不判断任何业务码,业务约定交给响应拦截器

安装

npm i dart-request dart-common

dart-common 是 peerDependency,必须一起安装。

快速开始

import request, { createRequest } from 'dart-request'

const api = createRequest({
  baseURL: 'https://api.example.com',
  timeout: 10000,
  headers: { Authorization: 'Bearer token' }
})

const { data, status } = await api.get('/users', { params: { page: 1, size: 20 } })
const created = await api.post('/users', { name: 'Alice' })

// 默认实例
const health = await request.get('/health')

API

createRequest(defaults?)

| 成员 | 说明 | | ------------------------------------- | --------------------------------------------------- | | request(config) / request(url) | 发起请求,config 可为字符串地址 + 配置对象 | | get / delete / head / options | 无请求体的快捷方法 | | post / put / patch | 带请求体的快捷方法,签名为 (url, data, config) | | defaults | 实例默认配置,可读写 | | interceptors.request / response | 拦截器管理器 | | create(childDefaults) | 基于当前配置创建子实例,不继承拦截器 | | fetch(input, init) | 原生 fetch 逃生舱,不套用 baseURL、拦截器与默认配置 |

顶层导出

request(默认实例)、createRequest、get、post、put、patch、del、head、options、RequestError、ErrorCode、createError、isRequestError、isCanceled。

delete 是保留字,因此顶层导出名为 del;实例上仍是 instance.delete()。

拦截器

| 方法 | 说明 | | -------------------------------- | ------------------- | | use(onFulfilled?, onRejected?) | 注册拦截器,返回 id | | remove(id) / eject(id) | 按 id 移除 | | clear() | 清空 | | size | 当前有效拦截器数量 |

  • 请求拦截器逆序执行(后注册的先跑),响应拦截器正序执行(先注册的先跑)
  • 回调返回 undefined 表示「原样放行」;请求拦截器返回非普通对象会以 ERR_INVALID_CONFIG 拒绝
  • 响应拦截器可以 return response.data 直接做业务解包
const api = createRequest({ baseURL: '/api' })

api.interceptors.request.use((config) => {
  config.headers.Authorization = `Bearer ${getToken()}`
  return config
})

api.interceptors.response.use((response) => {
  const body = response.data
  if (body && body.code === 'success') return body.data
  return response
})

配置项

| 字段 | 默认值 | 说明 | | -------------------- | ------------------------------------------------- | -------------------------------------------------------------------- | | url | '' | 请求地址,相对地址与 baseURL 拼接 | | method | 'GET' | 内部统一转大写 | | baseURL | '' | 基础地址 | | headers | { Accept: 'application/json, text/plain, */*' } | 值为 null 表示删除该头 | | params | null | 查询参数:对象 / URLSearchParams / 字符串 | | paramsSerializer | null | 自定义序列化函数或选项对象,false 表示禁用 | | data | undefined | 请求体 | | timeout | 0 | 毫秒,0 或 Infinity 表示不超时 | | withCredentials | false | 为 true 时映射成 credentials: 'include' | | credentials | undefined | 直接透传 fetch,优先级高于 withCredentials | | responseType | 'auto' | auto / json / text / blob / arrayBuffer / stream / raw | | validateStatus | 2xx | 成功判定,可自定义 | | transformRequest | [] | 请求体转换,内置 JSON 序列化最后执行 | | transformResponse | [] | 响应数据转换,在解析之后执行 | | onDownloadProgress | null | 下载进度回调 | | onUploadProgress | null | 原生 fetch 不支持,传了只会提示一次 | | signal | null | 取消信号 | | retry | false | 重试配置,true 表示使用默认参数 | | dedupe | false | 去重配置,true 表示对默认方法按 reject 策略去重 | | fetchOptions | null | 透传任意 fetch 选项 | | duplex | undefined | 流式请求体在 Node 下自动补 'half' | | meta | undefined | 业务自定义元数据,拦截器可读取,不传给 fetch |

mode / cache / redirect / referrer / referrerPolicy / integrity / keepalive 原样透传 fetch。

查询参数序列化

defaultParamsSerializer({ page: 1, tags: ['a', 'b'] }) // 'page=1&tags=a&tags=b'

| 选项 | 默认值 | 说明 | | ------------- | ---------- | ------------------------------------------------------------- | | arrayFormat | 'repeat' | repeat → a=1&a=2;brackets → a[]=1;comma → a=1,2 | | encode | true | encodeURIComponent,并保留 : $ , [ ] | | skipNull | true | 跳过 null / undefined,为 false 时输出 key= |

规则补充:Date 转 ISO 字符串;嵌套对象展开为 a[b]=c;空数组不输出;params 为字符串时去掉前导 ? 后直接追加;查询串始终插在 hash 之前。

响应解析

| responseType | 行为 | | -------------- | ----------------------------------------------------------------- | | auto | 按 content-type 判定;无类型时读取文本,{ / [ 开头尝试 JSON | | json | 严格 JSON.parse,失败抛 ERR_PARSE | | text | 文本,按 charset 解码 | | blob | Blob,环境不支持时抛 ERR_UNSUPPORTED | | arrayBuffer | ArrayBuffer | | stream | ReadableStream,不消费响应体,不支持进度与响应转换 | | raw | 原始 Response,不消费响应体 |

空响应(204 / 205 / 304、content-length: 0)一律返回 null。

返回值

const response = await api.get('/users')

response.data // 解析后的数据
response.status // 200
response.statusText
response.headers // Headers 实例
response.config // 最终生效的配置
response.request // { url, method, headers }
response.response // 原始 Response(读过进度时为缓冲区重建的实例)
response.retryCount // 重试次数,首次为 0

错误处理

import { ErrorCode, isCanceled } from 'dart-request'

try {
  await api.get('/orders')
} catch (error) {
  if (isCanceled(error)) return
  if (error.code === ErrorCode.HTTP_STATUS) console.log(error.status, error.response.data)
  throw error
}

| 错误码 | 含义 | | --------------------- | ----------------------------- | | ERR_INVALID_CONFIG | 配置非法 | | ERR_TIMEOUT | 超时 | | ERR_CANCELED | 被取消 | | ERR_NETWORK | 网络层失败(DNS、断网、CORS) | | ERR_HTTP_STATUS | 状态码未通过 validateStatus | | ERR_PARSE | 响应解析失败 | | ERR_DUPLICATE | 命中重复请求去重 | | ERR_RETRY_EXHAUSTED | 重试次数耗尽 | | ERR_UNSUPPORTED | 当前环境缺少必要的原生能力 |

RequestError 字段:code、status、response、config、request、cause、retryCount。超时不算取消,便于区分「用户放弃」与「服务端太慢」。

重试

await api.get('/report', { retry: { retries: 3, retryDelay: 300 } })

| 选项 | 默认值 | 说明 | | ------------------- | ----------------------------- | ------------------------------------ | | retries | 0 | 最大重试次数,0~10 | | retryDelay | 300 | 首次重试基础延迟 | | maxDelay | 5000 | 单次延迟上限 | | backoff | 2 | 指数退避倍数 | | jitter | 0.2 | 抖动比例,0 关闭 | | methods | GET/HEAD/OPTIONS/PUT/DELETE | 参与重试的方法 | | statusCodes | 408/429/500/502/503/504 | 参与重试的状态码 | | retryOn | null | 自定义判定,返回 true 覆盖默认规则 | | respectRetryAfter | true | 优先使用响应头 Retry-After |

只重试网络错误、超时与命中 statusCodes 的响应;用户已取消或请求体是 ReadableStream 时不重试。

重复请求去重

const api = createRequest({ dedupe: { strategy: 'share' } })
const [a, b] = await Promise.all([api.get('/dict'), api.get('/dict')]) // 只发一次

| 选项 | 默认值 | 说明 | | ---------- | ------------------ | ----------------------------------------------- | | strategy | 'reject' | 重复时抛 ERR_DUPLICATE / share 复用同一请求 | | key | null | 自定义去重键,返回空字符串表示跳过去重 | | methods | GET/HEAD/OPTIONS | 参与去重的方法 |

去重键 = method + 最终 URL + responseType + 请求体摘要,不包含 header,跨认证上下文请自定义 key。share 策略下所有调用方共享同一个结果对象,且只使用首个请求的 signal / timeout。

取消与超时

const controller = new AbortController()
api.get('/slow', { signal: controller.signal, timeout: 5000 })
controller.abort()

超时优先使用 AbortSignal.timeout + AbortSignal.any,缺失时回退 AbortController。超时会覆盖响应体读取阶段,定时器在请求结束(含失败)时清理。

下载进度

const res = await api.get('/export', {
  responseType: 'blob',
  onDownloadProgress: ({ loaded, total, percent }) => {
    console.log(percent === null ? `${loaded}B` : `${percent}%`)
  }
})

无 Content-Length 时 total 与 percent 为 null(lengthComputable: false)。读取进度后响应体会被缓冲区重建,content-encoding / content-length 会被移除。

浏览器与 Node 兼容性

| 能力 | 说明 | | ----------------- | -------------------------------------- | | fetch | Node 18+ / 现代浏览器 | | AbortSignal.any | 无原生实现时自动回退手写合并 | | ReadableStream | 缺失时下载进度不可用,请求本身仍可发 | | Blob | 缺失时 blob 解析抛 ERR_UNSUPPORTED | | Response | 缺失时不重建响应,直接用缓冲区解析 |

已知限制

  • 原生 fetch 无法获取上传进度,onUploadProgress 只提示一次,不做 XHR 降级
  • stream / raw 不支持 onDownloadProgress 与 transformResponse
  • 不做 GET 缓存、并发队列与 SSE
  • 去重键不含 header,share 策略的取消与超时只认首个请求
  • baseURL 只折叠边界斜杠,不做 .. 路径归一化
  • Content-Length 受压缩影响,percent 仅作展示参考
  • 浏览器禁止设置 Content-Length / Host 等头,设置会被忽略
  • FormData 不要手动设置 Content-Type,否则 boundary 会丢失

开发

npm run dev          # tsup watch
npm run build        # tsup + tsc 生成 .d.ts
npm run lint
npm run format

本地联调前需先构建同级依赖(该包当前没有 dist,import 'dart-common' 会解析失败):

cd ../npm-dart-common && npm run build

License

GPL-3.0-or-later