使用ts声明promisify函数(附部分TS进阶类型)

回调函数 Promise 化

举个例子:

1
2
3
4
5
6
const promisifyTimeOut = (timeout) =>
new Promise((resolve) => {
setTimeout(() => {
resolve();
}, time);
});

再例如 UniAPP 封装微信小程序的大部分 API,既可以通过 callback 进行回调,也可以使用 Promise 回调:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
uni.login({
success(res) {
if (res.code) {
//发起网络请求
} else {
console.log("登录失败!" + res.errMsg);
}
},
});
uni.login().then((res) => {
if (res.code) {
//发起网络请求
} else {
console.log("登录失败!" + res.errMsg);
}
});

如何在声明时通过判断传入的参数中是否有 success 回调判断返回值的类型?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
interface Res {
[key: string]: any;
}
interface Options {
[key: string]: any;
success?: (result: Res) => void;
fail?: (result: any) => void;
complete?: (result: any) => void;
}

type PromisifySuccessResult<
P,
T extends {
success?: (...args: any[]) => void;
},
R = void
> = P extends {
success: any;
}
? R
: P extends { fail: any }
? R
: P extends { complete: any }
? R
: Promise<Parameters<Exclude<T["success"], undefined>>[0]>;

type ProFunc = <T extends Options = Options>(
options?: T
) => PromisifySuccessResult<T, Options>;

(2023.06.01 更新)一种特殊情况,关于泛型约束的默认值

例子:

1
2
3
4
5
6
7
8
9
function handleDateFormat(date?: Date) {
if (!date) {
return;
}
return date.getTime();
}

const str = handleDateFormat(new Date()); // str: number | undefined
const str2 = handleDateFormat(); // str2: number | undefined

从函数实现逻辑来看,当传入参数时函数一定会返回一个 number 类型,只有当不传参数才会返回 undefined,但 ts 似乎并不能识别出来,这种情况下就需要使用函数重载去手动声明。

优化后:

1
2
3
4
5
6
7
8
9
10
11
12
function handleDateFormat<T extends Date | undefined>(
date?: T
): T extends Date ? number : undefined;
function handleDateFormat(date?: Date) {
if (!date) {
return;
}
return date.getTime();
}

const str = handleDateFormat(new Date()); // str: number
const str2 = handleDateFormat(); // str2: number | undefined

通过泛型约束,发现 str 已经能正确识别出返回值为 number 类型,但 str2 还是无法识别。

原因是声明中将 date 设置成了一个可选的参数,当 date 不传时 T 并没有获得类型,所以也没办法对返回值进行约束,如果调用改成:

1
const str2 = handleDateFormat(undefined); // str2: undefined

只有传入参数,哪怕是 undefined, str2 才能获得正确的类型。

那么如果让不传参数的使用也能正确返回 undefined 类型呢?我们只需要给泛型 T 一个默认值就可以:

1
2
3
4
5
6
7
8
9
10
11
12
function handleDateFormat<T extends Date | undefined = undefined>(
date?: T
): T extends Date ? number : undefined;
function handleDateFormat(date?: Date) {
if (!date) {
return;
}
return date.getTime();
}

const str = handleDateFormat(new Date()); // str: number
const str2 = handleDateFormat(); // str2: undefined

巧妙的点在于泛型约束的参数也能设置默认值,这样即使 date 参数没有传入,ts 依然能正确识别 T 的类型,从而判断正确的返回值类型。

涉及到的 TS 高级类型

条件类型约束

1
T extends U ? X : Y
1
type MessageOf<T extends { message: unknown }> = T["message"];

infer

infer 语法的限制如下:

infer 只能在条件类型的 extends 子句中使用
infer 得到的类型只能在 true 语句中使用, 即 X 中使用

1
2
3
4
5
type InferArray<T> = T extends (infer U)[] ? U : never; // 推断数组(或者元组)的类型
type InferFirst<T extends unknown[]> = T extends [infer P, ...infer _]
? P
: never; // 推断数组(或者元组)第一个元素的类型
type InferPromise<T> = T extends Promise<infer U> ? U : never; // 推断Promise成功值的类型

Parameters

1
2
3
4
5
type Parameters<T extends (...args: any) => any> = T extends (
...args: infer P
) => any
? P
: never;

Pick、Omit、Extract 和 Exclude

Extract<Type, Union>

提取 Type 中所有能够赋值给 Union 的属性,将这些属性构成一个新的类型

1
2
type Extract<T, U> = T extends U ? T : never;
type T0 = Extract<"a" | "b" | "c", "a" | "f">; // T0 = 'a'

Exclude<UnionType, ExcludedMembers>

从 UnionType 中去掉所有能够赋值给 ExcludedMembers 的属性,然后剩下的属性构成一个新的类型

1
2
type Exclude<T, U> = T extends U ? never : T;
type T0 = Exclude<"a" | "b" | "c", "a">; // T0 = 'b' | 'c'

Pick<Type, Keys>

从 Type 中选取一系列的属性,这些属性来自于 Keys(字符串字面量或字符串字面量的联合类型),用这些属性构成新的 type。

1
2
3
4
5
6
7
8
9
10
11
12
13
type Test = {
name: string;
age: number;
salary?: number;
};

//pick
type picked = Pick<Test, "name" | "age">;
// 结果
// type picked = {
// name: string;
// age: number;
// }

Omit<Type, Keys>

从 Type 中选取所有的属性值,然后移除属性名在 Keys 中的属性值

本质上是 Pick 的反向操作,排除掉 Keys。
Omit 相对而言复杂一些

1
2
3
4
5
6
7
8
9
10
11
type Test = {
name: string;
age: number;
salary?: number;
};
type omitted = Omit<Test, "age">;
// 结果
// type omitted = {
// name: string;
// salary?: number;
// }

但 omit 和 pick 在特殊情况下有一点区别:

1
2
type picked = Pick<"a" | "b", "a">; // 报错
type omitted = Omit<"a" | "b", "a">; // 不报错,且得到字符串上的方法

Omit

omit 源码:

1
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

T 如果是个对象,那么 typeof T 就是对象上的属性,那么 K 就和对象上的属性对比就可以,但如果 T 是个 string 类型,那么 keyof T 会获得字符串类型上的所有方法,K 自然不会匹配到这些方法,然后这个类型就变成了:

1
type omitted = Omit<"a" | "b", "a"> = Pick<"a" | "b", keyof "a" | "b"> = keyof "a" | "b"

使用ts声明promisify函数(附部分TS进阶类型)
https://www.wobushi.top/2023/使用ts声明promisify函数(附部分TS进阶类型)/
作者
Pride Su
发布于
2023年5月17日
许可协议