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

@rickyli79/abortable-executor

v0.1.1

Published

Runs fully-trusted async functions with an interruptible sandbox (abort / timeout support).

Readme

⏱️ @rickyli79/abortable-executor

Run fully-trusted async functions with an interruptible sandbox (abort / timeout).

为完全授信的 async 函数提供「可中断执行」的运行器:支持调用方手动 abort 与超时中断。隔离边界由入参沙箱构成——运行器把入参包进可撤销的 Proxy 再传给函数,从不入侵函数内部、也从不向函数传递 AbortSignal。中断时 revoke 整个沙箱(硬切断);成功时返回值中残留的 Proxy 就地换回真实对象,保持身份(===)。零运行时依赖,纯 TS(ESM),面向 Node.js。

npm version npm downloads


📑 目录


📖 项目简介

@rickyli79/abortable-executor 是为完全授信的 async 函数提供的「可中断执行」运行器。它围绕一个核心函数 run 展开:你把函数和它的入参交给它,它会保证这次调用可以被调用方(手动 abort)或超时(timeout)中断——而且完全不入侵函数内部。

隔离边界是入参沙箱:调用前,入参被包进可撤销的 Proxy。中断(signal abort 或 timeout)时会 revoke 整个沙箱——函数对入参的任何后续访问都会抛错,从而无法再污染外部。函数本身不会被停止,也永远不会收到 AbortSignal。

典型场景:

  • 给长时间运行的异步工作加一个硬超时或手动「停止」按钮。
  • 防止失控的函数继续写入它的入参、污染外部状态。
  • 用类型化、可区分的错误区分「调用方中止」和「超时」。

它没有任何运行时依赖——实现是纯 TypeScript(ESM,由 tsup 构建),只依赖 Node.js 内建模块(node:async_hooks,用于可选的 timer 劫持 addon)。

✨ 特性

  • ⏹️ 可中断执行:通过 AbortSignal 或 timeout 中断;返回的 Promise 会以类型化错误 reject(AbortError / TimeoutError,二者都继承自 AbortableError)。
  • 🛡️ 非入侵:函数签名完全保留——它永远不会收到 AbortSignal,内部也从不被改写。整个隔离边界就是入参沙箱。
  • 🔒 中断 = 硬切断:中断时 revoke 全部沙箱 Proxy;函数对入参的任何后续访问都会抛错,从而无法再污染外部。(函数本身并不停止。)
  • 🎯 成功 = 保身份:返回值中残留的 Proxy 会就地换回真实对象,保持 === 身份一致;成功路径不 revoke,不会留下「死 Proxy」。
  • 🆔 跨 run 净化:若一个 run 的沙箱 Proxy 被传进另一个 run,会先解包回真实对象再重新包裹——每次 run 完全独立,中断一个不会影响另一个。
  • 🥇 先到者赢:函数完成与中断同时发生时,先发生者决定结果。
  • 🧩 零运行时依赖、纯 TS(ESM):无任何第三方依赖,面向 Node.js。
  • ⏱️ 可选 captureTimers addon:在本次 run 内劫持 setTimeout/setInterval/setImmediate 与 Promise 构造函数(以 AsyncLocalStorage 圈定到本次 run 的异步后裔):abort 时清已登记 timer、abort 后创建 timer/Promise 抛 AbortError。默认关闭 = 零全局副作用。

📦 安装

使用 pnpm(本项目开发环境要求 pnpm ^11.18.0):

pnpm add @rickyli79/abortable-executor

也可以使用 npm 或 yarn:

npm install @rickyli79/abortable-executor
# 或
yarn add @rickyli79/abortable-executor

该包发布到 npm 公开注册表(https://registry.npmjs.org),publishConfig.access = public。

模块格式(仅 ESM)

该包只提供 ESM 构建(由 tsup 生成):dist/index.js,类型声明 dist/index.d.ts。包级配置:type: module。

由于依赖 node:async_hooks(用于可选的 captureTimers addon),它面向 Node.js。

import {
  run,
  AbortableError,
  AbortError,
  TimeoutError,
  isAbortableError,
} from "@rickyli79/abortable-executor";

🚀 快速开始

示例一:基本用法

函数签名完全保留——入参以元组形式传入:

import { run } from "@rickyli79/abortable-executor";

// async 函数,正常拿到返回值
const total = await run(async (a: number, b: number) => a + b, [1, 2]);
// total === 3

// 同步函数也可以
const upper = await run((s: string) => s.toUpperCase(), ["hi"]);
// upper === "HI"

// 无参函数
const ok = await run(async () => "ok", []);

示例二:abort / 超时中断与类型化错误

import {
  run,
  AbortableError,
  AbortError,
  TimeoutError,
  isAbortableError,
} from "@rickyli79/abortable-executor";

const ctrl = new AbortController();

try {
  const result = await run(
    async (payload) => {
      /* ... 长时间运行的授信工作 ... */
    },
    [payload],
    { timeout: 1000, signal: ctrl.signal },
  );
  // ...
} catch (e) {
  if (e instanceof TimeoutError) {
    // 超过 1000ms 超时
  } else if (e instanceof AbortError) {
    // 通过 ctrl.abort() 手动中止
  } else if (isAbortableError(e)) {
    // 任一中止域错误(即上面的两种)
  } else {
    // 函数自身抛出的错误,原样传播
  }
}

🧩 API 文档

包的核心导出是函数 run。

函数签名

function run<Func extends (...args: any[]) => any>(
  fn: Func,
  args: Parameters<Func>,
  opts?: RunOptions,
): Promise<Awaited<ReturnType<Func>>>;

参数

| 参数 | 类型 | 必填 | 说明 | | ------ | ------------------ | ---- | ---------------------------------------------------------------------------- | | fn | (...args) => any | 是 | 要执行的函数(async 或同步均可)。签名完全保留——永远不会收到 AbortSignal。 | | args | Parameters<fn> | 是 | 与 fn 参数一一对应的元组。 | | opts | RunOptions | 否 | 见下。 |

选项(RunOptions)

| 选项 | 类型 | 默认 | 说明 | | -------------------- | ------------- | ------- | -------------------------------------------------------------------------------------- | | opts.timeout | number (ms) | 无 | 超时毫秒数;undefined/Infinity = 不超时;<= 0 或 NaN 同步抛 TypeError。 | | opts.signal | AbortSignal | 无 | 手动中止信号(只给 runner 用,不传给函数)。启动时已 abort → 立即 reject,不调用函数。 | | opts.depth | number | 5 | 沙箱包裹深度;0 = 仅包顶层入参,嵌套对象是真实引用。 | | opts.captureTimers | boolean | false | 启用 timer/Promise 劫持 addon(见「语义与注意事项」);默认关闭 = 零全局副作用。 |

返回值

Promise<Awaited<ReturnType<fn>>>:

  • 正常完成时,resolve 为函数的返回值;其中残留的 Proxy 会被就地换回真实对象,保持与调用方对象的 === 身份一致,且不 revoke。
  • 中断时,以 AbortError(手动中止)或 TimeoutError(超时)reject。
  • 若函数自身抛错 / reject,则以相同原因 reject,原样传播。
  • 选项非法时同步抛 TypeError。

错误类型

| 类型 | 含义 | | ------------------------------------- | -------------- | | AbortableError | 中止域错误基类 | | AbortError extends AbortableError | 调用方手动中止 | | TimeoutError extends AbortableError | 超时自动中断 | | isAbortableError(e) | 中止域类型守卫 |

类型定义

export interface RunOptions {
  timeout?: number;
  signal?: AbortSignal;
  depth?: number;
  captureTimers?: boolean;
}

export class AbortableError extends Error {}
export class AbortError extends AbortableError {}
export class TimeoutError extends AbortableError {}

export function isAbortableError(e: unknown): e is AbortableError;

export function run<Func extends (...args: any[]) => any>(
  fn: Func,
  args: Parameters<Func>,
  opts?: RunOptions,
): Promise<Awaited<ReturnType<Func>>>;

⚠️ 语义与注意事项

  1. 非入侵:运行器从不入侵函数内部,也从不向函数传递 AbortSignal。整个隔离边界就是入参沙箱。
  2. 中断 = 硬切断:abort / 超时时 revoke 全部沙箱 Proxy——函数对入参的任何后续访问都会抛错,从而无法再污染外部。函数本身并不停止(CPU 死循环无法被终止)。
  3. 成功 = 保身份:成功时返回值中残留的 Proxy 会就地换回真实对象(=== 身份一致)。成功路径不 revoke,因此换不动的 Proxy(闭包、私有字段、WeakMap 键、冻结对象内)保持存活透明,不会变「死雷」。
  4. 跨 run 净化:若一个 run 的沙箱 Proxy 被传进另一个 run,会先解包回真实对象再重新包裹——每次 run 完全独立,中断一个不会影响另一个。
  5. 先到者赢:函数完成与中断同时发生时,先发生者决定结果。
  6. 启动即已 abort:调用时 signal 已 aborted → 立即以 AbortError reject,不调用函数。
  7. 选项校验:非法的 timeout(<= 0 或 NaN)或非 AbortSignal 的 signal 会同步抛 TypeError。
  8. 逃逸语义:若函数在中断前已把沙箱对象缓存到闭包/全局/WeakMap 中,中断只切断对入参的访问,无法回收那些已逃逸的引用(函数完全授信的前提)。

captureTimers addon

传入 captureTimers: true 时,本次 run 会劫持全局 setTimeout/setInterval/setImmediate 与 Promise 构造函数(以 AsyncLocalStorage 圈定到本次 run 的异步后裔,不影响无关代码):

  • abort 时:清掉函数内已登记的 timer(其回调不再触发)。
  • abort 后:函数内再创建 timer 或 new Promise 会抛 AbortError(响亮失败,而非假启动)。
  • 成功时:不清 timer(成功 = 正常完成,函数自己的后台 timer 归它管)。
  • 引用计数:第一个 captureTimers run 时 patch 全局,最后一个 settle 后恢复。
  • 边界:非 timer 的纯异步 promise(I/O)无法 fail fast,只能等它 settle;捕获了内建引用的库可绕过劫持(授信函数前提)。
await run(fn, args, { captureTimers: true, timeout: 1000 });

🌐 运行环境要求

  • 仅 Node.js:包依赖 node:async_hooks(AsyncLocalStorage,用于可选的 captureTimers addon),面向 Node.js(无浏览器构建)。
  • 仅 ESM:type: module,无 CJS 构建。
  • 零运行时依赖:运行时无任何第三方依赖。

🛠️ 开发

环境要求

  • Node.js(建议 ≥ 22.x)
  • pnpm ^11.18.0(devEngines.packageManager,不满足时会提示下载)

脚本

| 命令 | 说明 | | ---------------------------- | ----------------------------------------------------------------------------- | | pnpm run typecheck | 类型检查(tsc --noEmit) | | pnpm test | 运行测试(vitest run,当前共 25 个测试) | | pnpm run build | 构建(tsup,生成 ESM + d.ts) | | pnpm run lint | Lint(oxlint) | | pnpm run changelog | 生成 CHANGELOG.md(auto-changelog,keepachangelog 模板,起始版本 v0.1.0) | | pnpm run changelog:preview | 生成预览版 CHANGELOG-preview.md |

目录结构

abortable-executor/
├── src/
│   ├── index.ts          # 核心实现(run + 沙箱 + captureTimers addon)
│   └── index.test.ts     # vitest 测试(25 个用例)
├── vitest.config.ts      # 测试配置
├── tsconfig.json         # TypeScript 配置
├── CONTEXT.md            # 领域模型 / 术语
├── .github/workflows/    # CI / 自动发布(publish.yml + changelog-preview.yml)
└── package.json

📄 许可证

MIT © Ricky Li