打字稿键入一个包装函数,该函数采用包装函数的类型
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);
doWorkWrapped
与 doWork
具有相同的类型,即可以互换调用,将在内部调用 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.
概述和目的:
我正在尝试编写一个函数,它是作为参数传入的函数的包装器;其中所讨论的函数假设函数的类型作为参数传入。
这似乎是 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);
doWorkWrapped
与 doWork
具有相同的类型,即可以互换调用,将在内部调用 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.