Skip to content

Commit 3435874

Browse files
committed
新增:AnyCLass类型,新增了tryCatch的错误部分捕获
1 parent bf72114 commit 3435874

7 files changed

Lines changed: 211 additions & 61 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
- Enum()
4949
- EnumKeys<>
5050
- `type` anyobject
51+
- `type` AnyClass
5152
- Object.prototype
5253
- hasKeys()
5354
- inKeys()

bun.lock

Lines changed: 61 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,36 @@
11
{
22
"name": "@ceale/util",
3-
"version": "1.17.0",
4-
3+
"version": "1.18.0",
54
"author": "Ceale",
65
"description": "小工具集",
76
"repository": {
87
"url": "git+https://github.com/Ceale/ts-util.git"
98
},
10-
119
"license": "Unlicense or CC0 or WTFPL",
12-
"keywords": [ "util" ],
13-
10+
"keywords": ["util"],
1411
"type": "module",
1512
"module": "dist/esm/index.ts",
1613
"main": "dist/cjs/index.js",
1714
"types": "dist/types/index.d.ts",
1815
"files": ["dist"],
1916
"exports": {
2017
".": {
18+
"types": "./dist/types/index.d.ts",
2119
"import": "./dist/esm/index.js",
22-
"require": "./dist/cjs/index.js",
23-
"types": "./dist/types/index.d.ts"
20+
"require": "./dist/cjs/index.js"
2421
},
2522
"./package.json": "./package.json"
2623
},
27-
2824
"scripts": {
2925
"build": "bun run build:esm && bun run build:cjs && bun run build:ts",
30-
"build:cjs": "bun build --outdir=dist/cjs/ --format=cjs --target=node --packages=external src/index.ts",
31-
"build:esm": "bun build --outdir=dist/esm/ --format=esm --target=node --packages=external src/index.ts",
26+
"build:esm": "esbuild --outdir=dist/esm/ --format=esm --target=esnext --bundle --packages=external src/index.ts",
27+
"build:cjs": "esbuild --outdir=dist/cjs/ --format=cjs --target=esnext --bundle --packages=external src/index.ts",
3228
"build:ts": "tsc --project tsconfig.build.json"
3329
},
3430
"devDependencies": {
35-
"typescript": "^5",
36-
"@types/bun": "latest"
31+
"@types/bun": "latest",
32+
"esbuild": "^0.27.0",
33+
"typescript": "^5"
3734
},
3835
"dependencies": {
3936
"csstype": "^3.1.3"

src/class.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
// 定义一个通用的类/构造函数类型
2-
type AnyClass = new (...args: any[]) => any
1+
import type { AnyClass } from "./type"
32

43
declare global {
54
interface Function {

src/error.ts

Lines changed: 35 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { AnyClass } from "./type"
2+
13
type Success<T> = {
24
data: T
35
error: null
@@ -14,61 +16,61 @@ type Failure<E> = {
1416
error: E
1517
]
1618

17-
type Result<T, E extends unknown = Error> = Success<T> | Failure<E>
19+
type Result<T, E = any> = Success<T> | Failure<E>
1820

1921
interface tryCatch {
20-
<T>(arg: Promise<T>): Promise<Result<T>>
21-
<V>(arg: (() => V)): Result<V>
22+
// 捕获所有
23+
<T>(func: (() => T)): Result<T>
24+
<T>(asyncFunc: Promise<T>): Promise<Result<T>>
25+
// 捕获指定
26+
<T, E extends AnyClass>(func: (() => T), catchClass: E): Result<T, InstanceType<E>>
27+
<T, E extends AnyClass>(func: Promise<T>, catchClass: E): Promise<Result<T, InstanceType<E>>>
2228
}
2329

2430
/**
25-
* 尝试执行一个函数或Promise,返回一个包含执行结果或错误信息的复合对象
31+
* 尝试执行一个函数或Promise,返回一个对象,其中包含执行结果或被捕获的错误
2632
*
2733
* 该对象同时支持对象属性访问(.data/.error)和数组索引访问([0]/[1])两种方式
2834
*
29-
* @param parameter 一个函数或者Promise对象
30-
* @returns 返回参数中,data和[0]是函数的返回值或是Promise的解析结果,error和[1]是错误对象。两者将有一为`null`
35+
* @param func 一个函数或者Promise对象
36+
* @param catchClass 要捕获的错误类型(含子类),如果不指定,则捕获所有错误;如指定,则为被捕获的错误将继续上抛
37+
* @returns 返回一个如后方例的对象`{ data, error } & [ data, error ]`
3138
*
3239
* @example
3340
* tryCatch(() => func(...))
3441
* // => [data, error] & {data, error}
3542
* // data: func的返回值,error:函数执行过程中发生的错误
3643
*
44+
*
45+
* @example
46+
* tryCatch(() => func(...), TypeError)
47+
* // => [data, error] & {data, error}
48+
* // data: func的返回值,error:函数执行过程中,发生的TypeError或其子类错误
49+
*
3750
* @example
3851
* await tryCatch(asyncFunc(...))
3952
* // => [data, error] & {data, error}
40-
* // data: asyncFunc的返回值,error:asyncFunc执行过程中发生的错误
53+
* // data: 异步函数asyncFunc的返回值,error:asyncFunc执行过程中发生的错误
4154
*/
42-
export const tryCatch: tryCatch = (parameter: any): any => {
43-
44-
// 处理 Promise
45-
if (typeof parameter === "object") {
46-
return (parameter as Promise<any>)
47-
.then(data => Object.assign([ data, null ], { data, error: null }))
48-
.catch(error => Object.assign([ null, error ], { data: null, error }))
49-
}
55+
export const tryCatch: tryCatch = (func: Function | Promise<any>, catchClass?: any): any => {
5056

5157
// 处理函数
52-
if (typeof parameter === "function") {
58+
if (typeof func === "function") {
5359
try {
54-
const data = parameter()
60+
const data = func()
5561
return Object.assign([ data, null ], { data, error: null })
5662
} catch (error) {
57-
return Object.assign([ null, error ], { data: null, error })
63+
if (!catchClass || error instanceof catchClass) return Object.assign([ null, error ], { data: null, error })
64+
throw error
5865
}
59-
}
60-
61-
throw new TypeError("参数类型错误,应为 Promise 或函数")
62-
}
66+
} else {
67+
// 处理 Promise,兼容第三方Promise实现
68+
return func
69+
.then(data => Object.assign([ data, null ], { data, error: null }))
70+
.catch(error => {
71+
if (!catchClass || error instanceof catchClass) return Object.assign([ null, error ], { data: null, error })
72+
throw error
73+
})
6374

64-
// import fs from "node:fs/promises"
65-
// (async () => {
66-
// const a = await tryCatch(fs.stat("a.txt"))
67-
// console.log(typeof a)
68-
// })()
69-
// console.log(a)
70-
// if (!a.error) {
71-
// console.log(a.error)
72-
// } else {
73-
// console.log(a.data)
74-
// }
75+
}
76+
}

src/type.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
/**
2+
* 一种介于 `any` 与 `object` 之间的类型
3+
* - 与 `any` 类似,可以任意访问属性
4+
* - 与 `object` 类似,排除了原始类型
5+
*/
6+
export type anyobject = Record<any, any>
7+
8+
/**
9+
* 表示任意的类或构造函数
10+
*/
11+
export type AnyClass = new (...args: any[]) => any
12+
113
/**
214
* 就地断言一个变量为指定类型
315
* @template Type 断言的类型,默认为`any`
@@ -14,13 +26,6 @@ export const assert = <Type = any>(variable: any): asserts variable is Type => v
1426
*/
1527
export const expand = <Type>(variable: any): asserts variable is (typeof variable & Type) => variable
1628

17-
/**
18-
* 一种介于 `any` 与 `object` 之间的类型
19-
* - 与 `any` 类似,可以任意访问属性
20-
* - 与 `object` 类似,排除了原始类型
21-
*/
22-
export type anyobject = Record<any, any>
23-
2429
/**
2530
* 定义一个枚举类型。
2631
* 返回一个枚举对象,其中每个键名与值相同。
@@ -70,4 +75,5 @@ export const Enum = (keys: Record<string, string>) => ({
7075
* const 对吗 = defineEnum("对", "不对")
7176
* Enum<typeof 对吗> // "对" | "不对"
7277
*/
73-
export type EnumKeys<E> = E[keyof E]
78+
export type EnumKeys<E> = E[keyof E]
79+
// 定义一个通用的类/构造函数类型

test/error.test.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { describe, expect, test } from "bun:test";
2+
import { tryCatch } from "src/error"
3+
4+
// 定义一些自定义错误用于测试 catchClass
5+
class MyError extends Error {
6+
constructor(message: string) {
7+
super(message);
8+
this.name = "MyError";
9+
}
10+
}
11+
12+
class OtherError extends Error {
13+
constructor(message: string) {
14+
super(message);
15+
this.name = "OtherError";
16+
}
17+
}
18+
19+
describe("tryCatch 同步函数测试", () => {
20+
test("同步成功:应返回数据并支持数组/对象解构", () => {
21+
const [data, error] = tryCatch(() => "hello");
22+
const res = tryCatch(() => "hello");
23+
24+
expect(data).toBe("hello");
25+
expect(error).toBeNull();
26+
expect(res.data).toBe("hello");
27+
expect(res.error).toBeNull();
28+
});
29+
30+
test("同步失败(捕获所有):应返回错误", () => {
31+
const err = new Error("sync fail");
32+
const [data, error] = tryCatch(() => {
33+
throw err;
34+
});
35+
36+
expect(data).toBeNull();
37+
expect(error).toBe(err);
38+
});
39+
40+
test("同步失败(指定错误类):匹配时应捕获", () => {
41+
const err = new MyError("known error");
42+
const [data, error] = tryCatch(() => {
43+
throw err;
44+
}, MyError);
45+
46+
expect(data).toBeNull();
47+
expect(error).toBe(err);
48+
});
49+
50+
test("同步失败(指定错误类):不匹配时应抛出错误", () => {
51+
const err = new OtherError("unknown error");
52+
expect(() => {
53+
tryCatch(() => {
54+
throw err;
55+
}, MyError);
56+
}).toThrow(OtherError);
57+
});
58+
});
59+
60+
describe("tryCatch Promise 测试", () => {
61+
test("异步成功:应返回数据", async () => {
62+
const promise = Promise.resolve("async hello");
63+
const [data, error] = await tryCatch(promise);
64+
65+
expect(data).toBe("async hello");
66+
expect(error).toBeNull();
67+
});
68+
69+
test("异步失败(捕获所有):应返回错误", async () => {
70+
const err = new Error("async fail");
71+
const [data, error] = await tryCatch(Promise.reject(err));
72+
73+
expect(data).toBeNull();
74+
expect(error).toBe(err);
75+
});
76+
77+
test("异步失败(指定错误类):匹配时应捕获", async () => {
78+
const err = new MyError("async known error");
79+
const [data, error] = await tryCatch(Promise.reject(err), MyError);
80+
81+
expect(data).toBeNull();
82+
expect(error).toBe(err);
83+
});
84+
85+
test("异步失败(指定错误类):不匹配时应 reject", async () => {
86+
const err = new OtherError("async unknown error");
87+
// Bun 的 expect 支持 rejects 断言
88+
expect(tryCatch(Promise.reject(err), MyError)).rejects.toThrow(OtherError);
89+
});
90+
});

0 commit comments

Comments
 (0)