一次泛型丢失问题记录

问题来源

在实现一步一步实现一个基于信号量与队列的简单并发控制类 - 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

在入队的时候可以看到类型是可以追溯的
image

但是出队的时候泛型“丢失”了,或者说我不知道应该如何正确传递此泛型
image

最小化问题

上面所述问题本质上是下面这个问题:

const queue: any[] = [];

function inQueue<T>(someThing: T) {
  queue.push(someThing);
}
function outQueue() {
  const someThing = queue.pop()!;
}

inQueue("hi");
inQueue(123);

问题在于入队的时候,可以追踪到泛型T指的是inQueue调用时的类型,也就是例子中的stringnumber
image

但是在出队的时候,泛型T丢失了
image

解决方法

通过引入一个高阶函数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);

实现泛型追踪
image

原问题解决之后的完整代码

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();
    });
  }
}
posted @ 2024-03-20 15:06  Cat_Catcher  阅读(18)  评论(0编辑  收藏  举报
#