是否有可能实现类型安全的节点风格回调



节点回调如下:

interface NodeCallback<TResult,TError> {
  (err: TError): void;
  (err: null, res: TResult): void;
}

所以回调函数要么得到err要么得到res,但不能两者都得到。我看到的大多数类型都将errres的类型硬编码为它们的非可选版本。

function readdir(path: string, callback?: (err: NodeJS.ErrnoException, files: string[]) => void): void;

这不是严格类型安全的。例如,下面的代码编译得很好:

fs.readdir('/', (err, files) => {
  if (err !== null) { // There's an error!
    files.forEach(log); // Still using the result just fine.
  }
})

您可以通过更改签名来包含所有可能的值来使其更安全。

function readdir(path: string, callback?: (err: null | NodeJS.ErrnoException, files?: string[]) => void): void;

但是没有办法指定两者之间的依赖关系,所以你需要输入assert res来关闭strictNullChecks

fs.readdir('/', (err, files) => {
  if (err === null) { // There's no error
    // files.forEach(log); // Won't compile
    (files as string[]).forEach(log); // Type assertion
    files!.forEach(log); // Nice shorthand
    if (files !== undefined) { // Type guard
      files.forEach(log);
    }
  }
})

除了:

  • 当您需要重复执行时。
  • 当你没有访问一个属性,所以你类型断言,这可能意味着你需要导入另一个类型。真的很烦人。类型保护可以避免这种情况,但这样你就有了不必要的运行时间损失。
  • 它实际上仍然不安全。它更直接,所以你不得不考虑它,但我们主要依赖于手动断言。

如果你真的想这样做,你可以用一个类似Result的区分联合:

type Result<R,E>
  = { error: false, value: R }
  | { error: true, value: E }
function myFunction(callback: (res: Result<string, Error>) => void) {
  if (Math.random() > 0.5) {
    callback({ error: true, value: new Error('error!') });
  } else {
    callback({ error: false, value: 'ok!' })
  }
}
myFunction((res) => {
  if (res.error) {
    // type of res.value is narrowed to Error
  } else {
    // type of res.value is narrowed to string
  }
})

这真的很好,但是有很多样板文件,完全违背了通用节点风格。

所以我的问题是typescript目前是否有办法使这个超级常见的模式既类型安全又方便?我很确定现在的答案是否定的,这没什么大不了的,但我只是好奇。

谢谢!

除了你所做的,我看到的唯一好的模式是这样的:

function isOK<T>(err: Error | null, value: T | undefined): value is T {
    return !err;
}
declare function readdir(path: string, callback: (err: null | Error, files: string[] | undefined) => void): void;
readdir('foo', (err, files) => {
    if (isOK(err, files)) {
        files.slice(0);
    } else {
        // need to err! here but 'files' is 'undefined'
        console.log(err!.message);
    }
})

是的,这现在可以通过TypeScript 4.6特性的依赖参数控制流分析来实现。

以下是readdir的合适类型:

type readdir =
  ( path: string
  , cb: (...args: [NodeJS.ErrnoException, undefined] | [null, string[]]) => void
  ) => void
const myreaddir: readdir = fs.readdir as any

这捕获了您之前指出的问题:

myreaddir('/', (err, files) => {
  if (err !== null) {
    console.log(files.length)
             // └──── 18048: 'files' is possibly 'undefined'.
  }
})

最新更新