首页 > 解决方案 > Typescript - 为“promisifed”方法添加类型

问题描述

我有一个非常简单的模块,它包装了“fs”模块。该模块简单地“承诺”所有“fs”方法并导出新对象:

fsWrapper.ts

import Promise from "bluebird";
import * as fs from "fs";

const fsWrapper = Promise.promisifyAll(fs);

export = fsWrapper;

现在我可以使用这个包装器,而不是在每个调用者模块中“promisifiying”“fs”模块,如下所示:

main.ts

import fsWrapper from "./fsWrapper";

function test(): void {
    fsWrapper.readFileAsync("tst.txt", "utf8")
    .then((data: Buffer) => {
        console.log("data:", data.toString());
})

}

这当然不适用于打字稿,因为“fs”不包含该readFileAsync方法并且我收到编译器错误。

在搜索正确键入此包装器的方法时,我发现了以下打字稿问题

使用它的方法,我可以创建自己的fsWrapper.d.ts,我需要在其中手动添加 *Async 方法,类似于以下内容:

fsWrapper.d.ts

import Promise from "bluebird";

declare module "fs" {
    export function readFileAsync(path: string, options: { encoding?: string | null; flag?: string; } | string | undefined | null) : Promise<Buffer>;
    ...
}

这里的问题是:

  1. 手动添加所有需要的方法既乏味又容易出错。
  2. 如果这些方法在未来的版本中会改变,我不知道我的代码会继续编译,我会遇到运行时异常。

我知道这util.promisify是正确的类型,因此想到了用这些新方法以某种方式扩展“fs”,类似于以下内容:

fsWrapperTest.ts

import * as fs from "fs";
import { promisify } from "util";

let fsWrapper = fs;
fsWrapper.readFileAsync = promisify(fs.readFile);

export = fsWrapper;

但这会输出一个错误,表明我无法扩展现有模块: error TS2551: Property 'readFileAsync' does not exist on type 'typeof "fs"'. Did you mean 'readFileSync'?

有什么方法可以让我在保持“最新”输入和使用 Intellisense 的同时正确输入这个包装器?

编辑: 澄清一下,我了解如何创建一个对象,该对象将承诺所有“fs”方法(fsWrapper.ts上面的原始方法就是这种情况)。

我正在努力为 Intellisense 使用正确键入它。例如,运行以下命令:

import * as fs from "fs";
import { promisify } from "util";

let fsWrapper = Object.keys(fs).reduce((p: typeof fs, v: string) => { p[v] = promisify(fs[v]); return p }, {})

会给我一个fsWrapper: {}对象的类型。

我想将所有“fs”方法+新的“promisifed”方法作为它的类型。

编辑 - 选择的解决方案

我最终通过declare module 'fs'使用我在代码中使用的所有 *Async 方法扩展“fs”模块来使用该方法。不理想,但似乎没有更好的选择..

标签: node.jstypescript

解决方案


如果您使用的是 TypeScript 3.0 或更高版本,映射类型和参数元组的改进应该可以实现这一点。例如:

type UnpackedPromise<T> = T extends Promise<infer U> ? U : T
type GenericFunction<TS extends any[], R> = (...args: TS) => R
type Promisify<T> = {
    [K in keyof T]: T[K] extends GenericFunction<infer TS, infer R>
        ? (...args: TS) => Promise<UnpackedPromise<R>>
        : never
}

这会创建一个用于解包 Promise 的类型,一个用于泛型函数的类型,然后是一个用于 Promisified 版本的推断映射类型,该类型将每个键映射到一个新值,即前一个函数的 Promisified 版本。要使用该类型(游乐场链接):

const original = {
    one: () => 1,
    two: () => 2,
    add: (a: number, b: number) => a + b
}

const promisified = original as unknown as Promisify<typeof original>

promisified.one()
promisified.add(1, 2)

对于您的示例,以下是使用方法:

import * as fs from "fs";
import { promisify } from "util";

let fsWrapper = Object
    .keys(fs)
    .reduce((p: typeof fs, v: string) => { p[v] = promisify(fs[v]); return p }, {}) as unknown as Promisify<typeof fs>

请注意,您先转换为 unknown 然后再转换为Promisify<*>- 这是因为默认情况下它会认为映射类型可能是错误的。


推荐阅读