一次泛型丢失问题记录
问题来源
在实现一步一步实现一个基于信号量与队列的简单并发控制类 - Cat_Catcher - 博客园 (cnblogs.com)这篇文章中的并发控制类的时候,遇到了一个泛型丢失的问题。问题的核心在于入队的时候,对象成员的类型是:
type QueueMember<T> = {
toRequest: () => Promise<T>;
resolve: (value: T | PromiseLike<T>) => void;
reject: (reason?: any) => void;
};
<T>
的类型是请求方法返回的Promise的泛型,例如:async function requestProfile(uid: string): Promise<string>
中的string
。
在入队的时候可以看到类型是可以追溯的
但是出队的时候泛型“丢失”了,或者说我不知道应该如何正确传递此泛型
最小化问题
上面所述问题本质上是下面这个问题:
const queue: any[] = [];
function inQueue<T>(someThing: T) {
queue.push(someThing);
}
function outQueue() {
const someThing = queue.pop()!;
}
inQueue("hi");
inQueue(123);
问题在于入队的时候,可以追踪到泛型T
指的是inQueue
调用时的类型,也就是例子中的string
和number
但是在出队的时候,泛型T
丢失了
解决方法
通过引入一个高阶函数helper
,显式的指明泛型,使得ts的类型系统可以追踪到即可。
type Helper = <R>(helperCb: <T>(someThing: T) => R) => R;
const helper =
<T>(someThing: T): Helper =>
(helperCb) =>
helperCb(someThing);
const queue: Helper[] = [];
function inQueue<T>(someThing: T) {
queue.push(helper(someThing));
}
function outQueue() {
const getSomeThing = queue.pop()!;
getSomeThing((someThing) => {
someThing;
});
}
inQueue("hi");
inQueue(123);
实现泛型追踪
原问题解决之后的完整代码
async function requestProfile(uid: string): Promise<string> {
// 模拟用户头像请求
return new Promise((resolve) => {
setTimeout(() => {
resolve(`UserProfile-${uid}`);
}, 1000);
});
}
async function loadUserProfiles() {
const start = new Date().getTime();
const uids: string[] = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"];
const queue = new AsyncLimitQueue(5);
const profiles = await Promise.all(uids.map((id) => queue.inQueue(() => requestProfile(id))));
console.log(new Date().getTime() - start > 2000 && new Date().getTime() - start < 3000, profiles);
}
loadUserProfiles();
type QueueMember<T> = {
toRequest: () => Promise<T>;
resolve: (value: T | PromiseLike<T>) => void;
reject: (reason?: any) => void;
};
// 解决泛型丢失问题
type SomeQueueMember = <R>(cb: <T>(qm: QueueMember<T>) => R) => R;
const someQueueMember =
<T>(qm: QueueMember<T>): SomeQueueMember =>
(cb) =>
cb(qm);
class AsyncLimitQueue {
#limit = 7;
#waitingQueue: Array<SomeQueueMember> = [];
constructor(limit?: number) {
this.#limit = limit ?? this.#limit;
}
#execWaitingQueue() {
if (this.#limit === 0 || this.#waitingQueue.length === 0) return;
const sqm = this.#waitingQueue.shift()!;
sqm(({ toRequest, resolve, reject }) => {
this.#limit--; // 减少信号量
toRequest()
.then(resolve)
.catch(reject)
.finally(() => {
this.#limit++; // 增加信号量
this.#execWaitingQueue(); // 尝试执行等待队列中的请求
});
});
}
inQueue<T>(toRequest: () => Promise<T>): Promise<T> {
return new Promise<T>((resolve, reject) => {
// 所有请求先入队
this.#waitingQueue.push(
someQueueMember({
toRequest,
resolve,
reject,
})
);
// 尝试执行等待队列
this.#execWaitingQueue();
});
}
}
"Knowledge isn't free. You have to pay attention."