【问题标题】:Typescript typing a wrapping function that assumes the type of the function wrapped打字稿键入一个包装函数,该函数假定被包装的函数的类型
【发布时间】:2021-09-29 12:13:35
【问题描述】:

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

这似乎是打字稿中的一个常见用例,但是在搜索后似乎没有一个已发布的解决方案涵盖我的用例。

这是一个例子

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 函数,然后在完成时删除锁,然后返回来自doWork 的响应;同时让包装函数假定来自doWork 函数的输入和响应类型。

【问题讨论】:

  • 您想将带有参数的doWork 发送到acquireLock 吗?
  • 您能否添加一个示例,说明您希望如何调用这个新函数,也许是let response: DoWorkResponse = await wrapper(doWork, doWorkParams)
  • 您的意思是您对doWork 有多个不同的功能,并且您想将所有这些功能与“包装器”一起使用? LockParams 从哪里获取锁?是否所有DoWorkParams 都有一个id 字段?
  • 是否要求该包装器只接受doWork 或任何函数?
  • @Yoshi 正在寻找任何功能

标签: typescript typescript-typings


【解决方案1】:

因此,如果我理解正确,您想定义一个高阶函数,您可以传递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 相同类型的新函数,您需要一个再次返回(包装的)函数的包装函数:

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 并返回其结果,但带有锁定。

【讨论】:

  • 抱歉,这不是我想要的。问题标题中的内容是“假定包装函数类型的包装函数”。这意味着如果传递的函数是 doWork 或任何其他类型的函数,那么包装器应该假设参数的类型以及响应,无论类型声明是什么。
  • 您能否添加一个示例,说明您打算如何调用该函数、您将传入什么以及预期的结果是什么?
【解决方案2】:

这就是我最终想出的。

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 函数(或为此传入的任何函数)。

【讨论】:

  • 您的解决方案允许doWorkParams 中的额外属性。这可能是也可能不是问题。
【解决方案3】:

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

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.

【讨论】:

    猜你喜欢
    • 2017-12-14
    • 2021-02-24
    • 2013-05-23
    • 2017-12-01
    • 2021-12-25
    • 2021-04-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多