打字稿键入一个包装函数,该函数采用包装函数的类型

Typescript typing a wrapping function that assumes the type of the function wrapped

概述和目的:
我正在尝试编写一个函数,它是作为参数传入的函数的包装器;其中所讨论的函数假设函数的类型作为参数传入。

这似乎是 typescript 中的一个常见用例,但是在搜索之后,似乎 none 已发布的解决方案似乎涵盖了我的用例。

这是一个示例情况:

export interface LockParams {
  id: string;
}

export interface LockRecord {
  id: string;
  createdOn: string;
}

export async function acquireLock(params: LockParams): Promise<LockRecord> {
  // talk to database and return a value of LockRecord
}

export async function deleteLock(params: LockParams): Promise<void> {
  // talk to database and delete a lock record.
}

export interface DoWorkParams {
  id: string;
  customerName: string;
}

export interface DoWorkResponse {
  id: string;
  customerName: string;
  createdOn: string;
}

export async function doWork(params: DoWorkParams): Promise<DoWorkResponse> {
  // do work implementation 
}

我的问题: 我如何定义一个允许 doWork 和参数被发送给它的函数?在这种情况下,我希望所述函数获取锁,执行 doWork 函数,然后在完成后删除锁,然后 return 来自 doWork 的响应;所有同时让包装函数假定来自 doWork 函数的输入和响应类型。

因此,如果我理解正确的话,您想定义一个高阶函数,您可以将 doWork 传递给以使用锁定来执行它。可以这样做:

export async function doWorkWithLock(doWorkWorker: (params: DoWorkParams) => Promise<DoWorkResponse>, params: DoWorkParams): Promise<DoWorkResponse> {
  const lockParams = {id: params.id};

  const lockRecord = await acquireLock(lockParams);
  const workResponse = await doWorkWorker(params);
  await deleteLock(lockParams);

  return workResponse;
}

你可以这样 运行:

doWorkWithLock(doWork, doWorkParams);

您可能想知道的重要部分是 doWorkWorker 参数的类型,它必须与 doWork 函数的类型相同,即

(params: DoWorkParams) => Promise<DoWorkResponse>

为了提高可读性,您还可以为此定义一个类型别名:

type DoWorkWorker = (params: DoWorkParams) => Promise<DoWorkResponse>;

export async function doWorkWithLock(doWorkWorker: DoWorkWorker, params: DoWorkParams): Promise<DoWorkResponse> {
  // ...
}

doWorkWithLock(doWork, doWorkParams);

如果您 - 根据您的评论 - 需要一个与 doWork 类型相同的新函数,您需要一个包装函数,它再次 return 是一个(包装的)函数:

export function wrapWithLock(doWorkWorker: DoWorkWorker): DoWorkWorker {
  return async function wrappedWorker(params: DoWorkParams): Promise<DoWorkResponse> {
      const lockParams = {id: params.id};

      const lockRecord = await acquireLock(lockParams);
      const workResponse = await doWorkWorker(params);
      await deleteLock(lockParams);

      return workResponse;
  }
}

const doWorkWrapped = wrapWithLock(doWork);

doWorkWrappeddoWork 具有相同的类型,即可以互换调用,将在内部调用 doWork 和 return 其结果,但带有锁定。

这就是我最终得出的结论。

export async function doWorkWithLock<P, R>(
  doWorkParams: P,
  lockParams: LockParams,
  doWork: (i: P) => Promise<R>
): Promise<R> {
  await acquireLock(lockParams); // will throw error if unable to acquire lock

  try {
    return await doWork(doWorkParams);
  } catch (error) {
    // TODO: log error

    throw error;
  } finally {
    await deleteLock(lockParams);
  }
}

所以现在我可以使用此功能并执行以下操作:

const lockParams: LockParams = {
    id: "example id",
}

const doWorkParams: DoWorkParams = {
  id: "do work id",
  customerName: "example name,
}

doWorkWithLock(doWorkParams, lockParams, doWork)

doWorkWithLock 函数现在将假定输入 doWork 函数(或为此传入的任何函数)。

如果您愿意稍微更改您的要求,我认为以下内容允许非常简单的输入并提供更大的灵活性:

async function withLock1<T>(
  lockParams: LockParams,
  fn: () => T | Promise<T>
): Promise<T> {
  await acquireLock(lockParams);
  const r = await fn();
  await deleteLock(lockParams);
  return r;
}

// call
const responseA: DoWorkResponse = await withLock1(
  {id: 'foo'}, 
  () => doWork({id: 'bar', customerName: 'name'})
);

虽然如果你真的想把实际的工作函数传递给它的参数,这会起作用,甚至会因为函数参数的未知属性而出错:

async function withLock2<T extends (...args: any) => any>(
  lockParams: LockParams,
  fn: T,
  ...params: Parameters<T>
): Promise<ReturnType<T>> {
  await acquireLock(lockParams);
  const r = await fn.call(params);
  await deleteLock(lockParams);
  return r;
}

// call
const responseB: DoWorkResponse = await withLock2(
  {id: 'foo'}, 
  doWork, 
  {id: 'bar', customerName: 'name'}
);

两个版本都允许工人使用多个参数作为奖励。


注意:显然你可以改变实现(根据你自己的回答)。我选择的那个只是为了测试目的。

我唯一不得不妥协的是:

fn.call(params);

而不是:

fn(...params);

由于错误,其他人可能会指出。

// TS2488: Type 'Parameters ' must have a '[Symbol.iterator]()' method that returns an iterator.