首页 > 解决方案 > 类型可以被promsified的函数

问题描述

我正在努力向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;

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

标签: node.jstypescriptdefinitelytypednode-promisify

解决方案


鉴于我只能控制添加键入(.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;

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


推荐阅读