打字稿-为“承诺的”方法添加类型

时间:2018-10-07 17:54:47

标签: node.js typescript

我有一个非常简单的模块,其中包装了“ fs”模块。 此模块仅“允许”所有“ fs”方法并导出新对象:

fsWrapper.ts

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

const fsWrapper = Promise.promisifyAll(fs);

export = fsWrapper;

现在,我可以使用此包装器来代替在每个调用者模块中“承诺”使用“ 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方法,并且我收到编译器错误。

在搜索正确键入此包装器的方式时,我发现了following typescript issue

使用它的方法,我可以创建自己的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'模块。 不是很理想,但似乎没有更好的选择。

3 个答案:

答案 0 :(得分:1)

在github上有一个专门用于此目的的线程,请参见https://github.com/Microsoft/TypeScript/issues/8685

因此,您可以执行以下操作:

import { Promise } from 'bluebird';
import * as fs from "fs";

declare module "fs" {
    interface fs {
        [method: string]: any;
    }
}

const fsWrapper = Promise.promisifyAll(fs);
export = fsWrapper;

这与您为解决方法调用问题所做的工作非常相似。

请注意,据我所知,唯一允许完整Intellisense的方法是按照打字稿文档https://www.typescriptlang.org/docs/handbook/declaration-merging.html

中所述进行模块扩充

但是,这意味着您需要手动为上面的模块结构内部的promisified生成的新方法编写接口。即

declare module "fs" {
    interface fs {
        readFileAsync(... ),
        writeFileAsyng(... ),
        etc ...
    }
}

答案 1 :(得分:1)

如果您使用的是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的类型,一个泛型函数的类型,然后为有承诺的版本创建一个推断的映射类型,该映射类型将每个键映射到一个新值,该值是前一个函数的有承诺的版本。要使用该类型(playground link):

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>

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

答案 2 :(得分:1)

Node.js> = v10.x开始提供已经承诺的功能。

您可以通过require("fs").promisesrequire("fs/promises")(> = v14.x)使用​​它们

https://nodejs.org/docs/latest-v10.x/api/fs.html#fs_fs_promises_api https://nodejs.org/docs/latest-v14.x/api/fs.html#fs_fs_promises_api

我用

const fs = { ...require("fs"), ...require("fs/promises") };