0

我正在努力向node-vagrant NPM 库添加单独的类型以在 Typescript 中使用,以在 DefinitiveTyped 下贡献。但是,库的方法之一是promisify,它使库中的所有其他函数返回一个承诺而不是回调。

鉴于我只能控制添加键入(.d.ts)文件,有没有办法表明tsc调用该自定义 promisify 函数的结果是在函数上,或者是其他一些动态机制?或者只是我为函数的两种用法提供了类型,而用户必须确保他们选择正确?

一个最小的例子是 JS 文件是:

module.exports.foo = function (cb) {
    cb(null, 'foo');
};

module.exports.promisify = function () {
  module.exports.foo = util.promisify(module.exports.foo);
}

我的打字(在.d.ts文件中)是:

export function foo(cb: (err: null | string, out: string) => void): void;
export function promisify(): void;

现在,当我使用打字时:

import foo = require('foo');

foo.foo((err, out) => { console.log(err, out); });
foo.promisify();
foo.foo().then(out => console.log(out)).catch(err => console.log(err));

最后一行在 TSC 中引发错误。解决方案是在函数签名上同时声明回调和承诺,并让最终用户适当地决定使用哪一个,或者 TypeScript 中是否有某种机制来动态切换函数的返回信息?

综上所述,最后的判决只是在做:

export function foo(): Promise<string>;
export function foo(cb: (err: null | string, out: string) => void): void;

并且如上所述,只是让最终用户弄清楚他们想要回调还是承诺?

4

2 回答 2

1

鉴于我只能控制添加键入(.d.ts)文件,有没有办法表明tsc调用该自定义 promisify 函数的结果是在函数上,或者是其他一些动态机制?

据我所知,这对于 TypeScript 是不可能的。的类型foo是静态的,虽然 TypeScript 进行了一些基于控制流的类型分析,但它不能foo使用函数调用来切换类型。


如果promisify()返回一个包含承诺函数的新对象而不是就地切换它们,那么为此创建准确的类型定义会容易得多。但是由于您无法控制源,我看到的唯一两个选项是:

重载

这是您在问题中已经提到的解决方案。通过为每个方法声明两个签名,所有用法都将是有效的,但用户有责任确保他们promisify在想要使用 Promise 时调用。

export function foo(): Promise<string>;
export function foo(cb: (err: null | string, out: string) => void): void;

联盟

或者,整个模块可以导出一个联合类型,要求消费者在使用承诺的 API 之前对其进行转换。它可能看起来像这样:

export interface CallbackFoo {
    foo(cb: (err: null | string, out: string) => void): void;
    promisify(): void;
}

export interface PromiseFoo {
    foo(): Promise<string>;
    promisify(): void;
}

declare const _: CallbackFoo | PromiseFoo;
export default _;

以及用法:

import foo, { PromiseFoo } from 'foo';

foo.foo((err, out) => { console.log(err, out); });
foo.promisify();
(foo as PromiseFoo).foo().then(out => console.log(out)).catch(err => console.log(err));

现在您可能不想在使用它时一直投射它。一个更简洁的解决方案可能是创建一个单独的文件来导入foo、调用promisify和导出它:

import foo, { PromiseFoo } from './foo';

foo.promisify();

export default foo as PromiseFoo;

然而,这当然必须站在最终用户的一边。

于 2019-09-21T08:23:42.057 回答
0

@types/node 使用以下定义util.promisify

    interface CustomPromisify<TCustom extends Function> extends Function {
        __promisify__: TCustom;
    }

    function callbackify(fn: () => Promise<void>): (callback: (err: NodeJS.ErrnoException) => void) => void;
    function callbackify<TResult>(fn: () => Promise<TResult>): (callback: (err: NodeJS.ErrnoException, result: TResult) => void) => void;
    function callbackify<T1>(fn: (arg1: T1) => Promise<void>): (arg1: T1, callback: (err: NodeJS.ErrnoException) => void) => void;
    function callbackify<T1, TResult>(fn: (arg1: T1) => Promise<TResult>): (arg1: T1, callback: (err: NodeJS.ErrnoException, result: TResult) => void) => void;
    function callbackify<T1, T2>(fn: (arg1: T1, arg2: T2) => Promise<void>): (arg1: T1, arg2: T2, callback: (err: NodeJS.ErrnoException) => void) => void;
    function callbackify<T1, T2, TResult>(fn: (arg1: T1, arg2: T2) => Promise<TResult>): (arg1: T1, arg2: T2, callback: (err: NodeJS.ErrnoException | null, result: TResult) => void) => void;
    function callbackify<T1, T2, T3>(fn: (arg1: T1, arg2: T2, arg3: T3) => Promise<void>): (arg1: T1, arg2: T2, arg3: T3, callback: (err: NodeJS.ErrnoException) => void) => void;
    function callbackify<T1, T2, T3, TResult>(
        fn: (arg1: T1, arg2: T2, arg3: T3) => Promise<TResult>): (arg1: T1, arg2: T2, arg3: T3, callback: (err: NodeJS.ErrnoException | null, result: TResult) => void) => void;
    function callbackify<T1, T2, T3, T4>(
        fn: (arg1: T1, arg2: T2, arg3: T3, arg4: T4) => Promise<void>): (arg1: T1, arg2: T2, arg3: T3, arg4: T4, callback: (err: NodeJS.ErrnoException) => void) => void;
    function callbackify<T1, T2, T3, T4, TResult>(
        fn: (arg1: T1, arg2: T2, arg3: T3, arg4: T4) => Promise<TResult>): (arg1: T1, arg2: T2, arg3: T3, arg4: T4, callback: (err: NodeJS.ErrnoException | null, result: TResult) => void) => void;
    function callbackify<T1, T2, T3, T4, T5>(
        fn: (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5) => Promise<void>): (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, callback: (err: NodeJS.ErrnoException) => void) => void;
    function callbackify<T1, T2, T3, T4, T5, TResult>(
        fn: (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5) => Promise<TResult>,
    ): (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, callback: (err: NodeJS.ErrnoException | null, result: TResult) => void) => void;
    function callbackify<T1, T2, T3, T4, T5, T6>(
        fn: (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6) => Promise<void>,
    ): (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, callback: (err: NodeJS.ErrnoException) => void) => void;
    function callbackify<T1, T2, T3, T4, T5, T6, TResult>(
        fn: (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6) => Promise<TResult>
    ): (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, callback: (err: NodeJS.ErrnoException | null, result: TResult) => void) => void;

    function promisify<TCustom extends Function>(fn: CustomPromisify<TCustom>): TCustom;
    function promisify<TResult>(fn: (callback: (err: Error | null, result: TResult) => void) => void): () => Promise<TResult>;
    function promisify(fn: (callback: (err?: Error | null) => void) => void): () => Promise<void>;
    function promisify<T1, TResult>(fn: (arg1: T1, callback: (err: Error | null, result: TResult) => void) => void): (arg1: T1) => Promise<TResult>;
    function promisify<T1>(fn: (arg1: T1, callback: (err?: Error | null) => void) => void): (arg1: T1) => Promise<void>;
    function promisify<T1, T2, TResult>(fn: (arg1: T1, arg2: T2, callback: (err: Error | null, result: TResult) => void) => void): (arg1: T1, arg2: T2) => Promise<TResult>;
    function promisify<T1, T2>(fn: (arg1: T1, arg2: T2, callback: (err?: Error | null) => void) => void): (arg1: T1, arg2: T2) => Promise<void>;
    function promisify<T1, T2, T3, TResult>(fn: (arg1: T1, arg2: T2, arg3: T3, callback: (err: Error | null, result: TResult) => void) => void): (arg1: T1, arg2: T2, arg3: T3) => Promise<TResult>;
    function promisify<T1, T2, T3>(fn: (arg1: T1, arg2: T2, arg3: T3, callback: (err?: Error | null) => void) => void): (arg1: T1, arg2: T2, arg3: T3) => Promise<void>;
    function promisify<T1, T2, T3, T4, TResult>(
        fn: (arg1: T1, arg2: T2, arg3: T3, arg4: T4, callback: (err: Error | null, result: TResult) => void) => void,
    ): (arg1: T1, arg2: T2, arg3: T3, arg4: T4) => Promise<TResult>;
    function promisify<T1, T2, T3, T4>(fn: (arg1: T1, arg2: T2, arg3: T3, arg4: T4, callback: (err?: Error | null) => void) => void): (arg1: T1, arg2: T2, arg3: T3, arg4: T4) => Promise<void>;
    function promisify<T1, T2, T3, T4, T5, TResult>(
        fn: (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, callback: (err: Error | null, result: TResult) => void) => void,
    ): (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5) => Promise<TResult>;
    function promisify<T1, T2, T3, T4, T5>(
        fn: (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, callback: (err?: Error | null) => void) => void,
    ): (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5) => Promise<void>;
    function promisify(fn: Function): Function;

这是处理默认 Promisify 的一种非常完整的方法。你可能可以把它去掉一点。

于 2019-09-20T16:17:56.790 回答