typescript - 是否可以在从回调和参数列表中创建承诺的函数上创建类型安全?
问题描述
我已经定义了一个函数,它接受另一个函数和一个参数列表作为参数来创建一个 Promise,如下所示:
async callbackToPromise (func: Function, ...args: any[]): Promise<any> {
// Immediately return if the function argument is undefined, to avoid errors.
if (func === undefined) {
console.warn('Function undefined in callbackToPromise.')
return await Promise.reject(
new Error('Function undefined in callbackToPromise.')
)
}
const call = new Promise((resolve, reject) => {
func((resolveStr: unknown) => {
if (resolveStr !== undefined) {
return resolve(resolveStr)
} else {
return reject(new Error('No data returned'))
}
}, ...args)
})
return await call
}
我使用此函数从我的环境中的各种 API 调用中获得承诺,它们都将回调作为第一个参数并且不返回任何内容(只是用数据调用回调)。它们具有各种附加参数,并以各种返回类型调用回调。
它通常运行良好,但有时在编写我以前没有使用过的新 API 调用时我会遇到一些挫折,因为 Typescript 无法告诉我要传递哪些参数...args
,我必须花额外的时间检查我构建的类型让 API 准确地知道要传递什么以及如何传递。
在使用中,调用的函数callbackToPromise
都定义了自己的参数,这就是我在接口代码之外使用的,但是如果我也可以在那里保持类型安全,那么在定义新的接口函数时会更方便。如果我意识到我的 API 类型文件中的输入错误或不完整并且需要更新,那么就不容易出错。
有没有办法告诉 Typescript“只接受...args
匹配我传入的函数的参数func
?
额外细节:
我正在callbackToPromise
使用我无法访问的黑盒函数调用,例如具有这样签名的函数(它作为 的方法存在window.external
):
RemoveProblem: (
callback: Function,
prid: number,
stopDate: string,
isApproxDate: boolean,
reason: TProblemRemoveReason
) => void
我如何在我的代码中使用它的一个例子(更长的函数定义的一部分):
const result: number = await this.helpers
.callbackToPromise(
this.wrappedWindow.external.RemoveProblem,
prid,
stopDate,
isApprox,
reason
)
.catch((error: Error) => {
console.error(`Error removing problem: ${error.message}`)
})
理想情况下,callbackToPromise
如果我尝试传递与作为第一个参数传递的函数不匹配的参数,我希望在编译时给出类型错误。
旁注:当我最初尝试在下面实现 CRice 的答案时,我遇到了一个问题,当我尝试使用 实际调用函数时await
,Typescript 会说Type 'number | void' is not assignable to type 'number'. Type 'void' is not assignable to type 'number'.
这最终不是由于Parameter<T>
脚本的一部分,而是由于返回.catch
不返回值的部分。
解决方案
首先我要指出,如果您使用的是 nodejs,您可以util.promisify
为此目的使用内置函数,并且它已经带有正确的类型。在浏览器中,您可以使用许多软件包来实现相同的效果。但是,您也可以修改函数以使用泛型推断承诺类型。
这大量使用了提取类型参数类型的辅助类型Parameters<F>
F
(假设F
是函数类型)。
它的重要部分是您可以使用Parameters
辅助类型来提取您的函数将接受的回调的第一个参数的类型。这是您的承诺将解决的类型。
const callbackToPromise = async <A extends any[], F extends (CB: (result: any) => any, ...args: A) => any>(func: F, ...args: A): Promise<Parameters<Parameters<F>[0]>[0]> => {
// Immediately return if the function argument is undefined, to avoid errors.
if (func === undefined) {
console.warn('Function undefined in callbackToPromise.')
return await Promise.reject(
new Error('Function undefined in callbackToPromise.')
)
}
const call = new Promise((resolve, reject) => {
func((resolveStr) => {
if (resolveStr !== undefined) {
return resolve(resolveStr)
} else {
return reject(new Error('No data returned'))
}
}, ...args)
})
return call
}
那里有一些东西要解压,但这是发生的事情:
A extends any[]
声明一个泛型A
,它是任何其他类型的数组。稍后将使用它来表示除第一个参数之外的所有func
参数的类型。F extends (CB: (result: any) => any, ...args: A) => any
声明了一个额外的 genericF
,它是一个接受回调作为其第一个参数的函数,然后使用早期的泛型A
来表示所有剩余的参数。
最后返回类型:
Promise<Parameters<Parameters<F>[0]>[0]>
只是说承诺将解析为函数F
接受的回调的第一个参数的类型。
使用该定义,您似乎在使用它时得到了正确的推论:
const numericCallback = (cb: (v: number) => void, num: number): void => {
cb(num);
}
const promisifyNumericCallback = callbackToPromise(numericCallback, 56) // This is inferred as a Promise<number>
推荐阅读
- reactjs - React 功能组件状态变化不会触发子组件重新读取其 props
- php - 根据PHP更改所有页面上的链接
- swift - AppStore 查看崩溃日志
- cassandra - 我可以将计数器类型字段用作 C* 表的主键吗?
- r - 如何解决错误:无法在 RStudio 中分配大小为 70.7 Gb 的向量?
- python-3.x - ITM(爱尔兰横坐标)转换为谷歌地图 Python3 的 GPS
- embedded - 在 Eclipse IDE 上使用带有 MBED 库的矢量偏移表
- gremlin - 如何使用 Gremlin 查询获取特定标签的源顶点和目标顶点之间的所有传入中间顶点的列表?
- javascript - 尽管使用了等待,但异步函数返回待处理的承诺
- javascript - div 的 sessionStorage css 属性