@api3/promise-utils

中文文档
0.4.0发布于 2023/03/21MITESM + CJS零依赖近 30 天下载 3432

A simple package for a functional and typesafe error handling with zero dependencies

原始使用文档
文档目录10 节
  1. Installation
  2. Usage
  3. API
  4. Motivation
  5. Verbosity and interoperability of try-catch pattern
  6. Consistent throwing of an Error instance
  7. Intentionally limited feature set
  8. Limitations
  9. Developer documentation
  10. Release

promise-utils ContinuousBuild

A simple package for a functional and typesafe error handling with zero dependencies

Installation

To install this package run either:

yarn add @api3/promise-utils

or if you use npm:

npm install @api3/promise-utils --save

Usage

The API is small and well focused on providing more concise error handling. The main functions of this package are go and goSync functions. They accept a function to execute, and additionally go accepts an optional GoAsyncOptions object as the second parameter. If the function executes without an error, a success response with the data is returned, otherwise an error response is returned.

// Imagine an async function for fetching API data
const goFetchData = await go(() => fetchData('users'));
// The "goFetchData" value is either: {success: true, data: ...} or {success: false, error: ...}
if (goFetchData.success) {
  const data = goFetchData.data
  ...
}

or:

// Imagine an async function for fetching API data
// If the fetch data is a non class function returning a promise, you can drop the arrow function
const goFetchData = await go(() => fetchData('users'));
// The "goFetchData" value is either: {success: true, data: ...} or {success: false, error: ...}
if (!goFetchData.success) {
  const error = goFetchData.error
  ...
}

and with GoAsyncOptions:

// The `fetchData` function will be retried a maximum of 2 times on error, with each attempt having
// a timeout of 5 seconds and a total timeout 10 seconds (shared among all attempts and delays).
const goFetchData = await go(() => fetchData('users'), { retries: 2, attemptTimeoutMs: 5_000, totalTimeoutMs: 10_000 });
...

and for synchronous functions:

const someData = ...
// Imagine a synchronous function for parsing data
const goParseData = goSync(() => parseData(someData));
// The goParseData value is either: {success: true, data: ...} or {success: false, error: ...}
if (goParseData.success) {
  const data = goParseData.data
  ...
}

The return value from the promise utils functions works very well with TypeScript inference. When you check the the success property, TypeScript will infer the correct response type.

API

The full promise-utils API consists of the following functions:

  • go(asyncFn, options) - Executes the asyncFn and returns a response of type GoResult
  • goSync(fn) - Executes the fn and returns a response of type GoResult
  • assertGoSuccess(goRes) - Verifies that the goRes is a success response (GoResultSuccess type) and throws otherwise.
  • assertGoError(goRes) - Verifies that the goRes is an error response (GoResultError type) and throws otherwise.
  • success(value) - Creates a successful result value, specifically {success: true, data: value}
  • fail(error) - Creates an error result, specifically {success: false, error: error}

and the following Typescript types:

  • type GoResult<T> = { data: T; success: true };
    
  • type GoResultSuccess<E extends Error = Error> = { error: E; success: false };
    
  • type GoResultError<T, E extends Error = Error> = GoResultSuccess<T> | GoResultError<E>;
    
  • interface GoAsyncOptions<E extends Error = Error> {
      retries?: number; // Number of retries to attempt if the go callback is unsuccessful.
      attemptTimeoutMs?: number | number[]; // The timeout for each attempt. Can provide an array for different timeouts for each attempt. If the array is shorter than the number of retries, the last value is used for all remaining attempts, if the length of the array is longer than the number of retries, the extra values are ignored.
      totalTimeoutMs?: number; // The maximum timeout for all attempts and delays. No more retries are performed after this timeout.
      delay?: StaticDelayOptions | RandomDelayOptions; // Type of the delay before each attempt. There is no delay before the first request.
      onAttemptError?: (goRes: GoResultError<E>) => void; // Callback invoked after each failed attempt is completed. This callback does not fire for the last attempt or when a "totalTimeoutMs" is exceeded (these should be handled explicitly with the result of "go" call).
    }
    
  • interface StaticDelayOptions {
      type: 'static';
      delayMs: number;
    }
    
  • interface RandomDelayOptions {
      type: 'random';
      minDelayMs: number;
      maxDelayMs: number;
    }
    

Careful, the attemptTimeoutMs value of 0 means timeout of 0 ms. If you want to have infinite timeout omit the key or set it to undefined.

The last exported value is a GoWrappedError class which wraps an error which happens in go callback. The difference between GoWrappedError and regular Error class is that you can access GoWrappedError.reason to get the original value which was thrown by the function.

Take a look at the implementation and tests for detailed examples and usage.

Motivation

Verbosity and interoperability of try-catch pattern

// Verbose try catch
try {
  const data = await someAsyncCall();
  ...
} catch (e) {
  // The "e" is "unknown" because any value can be thrown in Javascript so casting is needed
  return logError((e as MyError).reason);
}

// Compare it to simpler version using go
const goRes = await go<MyData, MyError>(someAsyncCall);
if (!goRes.success) return logError(goRes.error.reason);
// At this point TypeScript infers that the error was handled and "goRes" must be a success response
const data = goRes.data;
...

Also, think about what happens when you want to handle multiple "can fail" operations in a single function call. You can either:

  1. Have them in a same try catch block - but then it's difficult to differentiate between what error has been thrown. Also this usually leads to a lot of code inside a try block and the catch clause acts more like "catch anything".
  2. Use nested try catch blocks - but this hurts readability and forces you into the callback hell pattern.

Consistent throwing of an Error instance

JavaScript supports throwing any expression, not just Error instances. This is also a reason why TypeScript infers the error as unknown or any (see: useUnknownInCatchVariables).

The error response from go and goSync always return an instance of the Error class. Of course, throwing custom errors (derived from Error) is supported.

Intentionally limited feature set

The go utils by design offer only very basic timeout and retry capabilities as these are often application specific and could quickly result in bloated configuration. If you are looking for more complex features, consider using one of the alternatives, e.g. https://github.com/lifeomic/attempt

Limitations

There is a limitation when using class functions due to how javascript this works.

class MyClass {
  constructor() {}
  get() {
    return this._get();
  }
  _get() {
    return '123';
  }
}

const myClass = new MyClass();
const resWorks = goSync(() => myClass.get()); // This works
// However, seeing the line above it may be tempting to rewrite it to
const resFails = goSync(myClass.get); // This doesn't work

The problem is that the this keyword is determined by how a function is called and in the second example, the this inside the get function is undefined which makes the this._get() throw an error.

Developer documentation

Release

To release a new version follow these steps:

  1. yarn && yarn build
  2. yarn version and choose the version to be released
  3. yarn publish --access public
  4. git push --follow-tags

版本与发布记录

@api3/promise-utils 共发布 4 个版本,首次发布于 2022/03/15;过去 12 个月发布了 0 个版本。

版本发布日期距今
0.4.0latest2023/03/213 年前
0.3.02022/04/234 年前
0.2.02022/04/084 年前
0.1.02022/03/154 年前

关于 @api3/promise-utils 的常见问题

@api3/promise-utils 是什么?

A simple package for a functional and typesafe error handling with zero dependencies

如何安装 @api3/promise-utils?

在项目目录运行 npm install @api3/promise-utils,或使用 pnpm add / yarn add / bun add。

@api3/promise-utils 支持 TypeScript 吗?

该包的 package.json 没有声明类型入口。需要类型提示时,可以在 npm 上查找社区维护的 @types/api3__promise-utils 是否存在。

@api3/promise-utils 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@api3/promise-utils 可以商用吗?

@api3/promise-utils 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@api3/promise-utils 的下载量有多少?

根据 npm 官方下载统计,@api3/promise-utils 在 2026/10/06 – 2026/11/04 这 30 天内被下载 3,432 次,日均约 114 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@api3/promise-utils 还在维护吗?

最新版本 0.4.0 发布于 2023/03/21(3 年前);至今共发布 4 个版本,其中过去 12 个月发布了 0 个;首次发布于 2022/03/15。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。