是否可以进行类型安全的节点式回调?

Ale*_*rra 3 typescript

节点回调看起来像:

interface NodeCallback<TResult,TError> {
  (err: TError): void;
  (err: null, res: TResult): void;
}
Run Code Online (Sandbox Code Playgroud)

所以回调要么得到err要么res不是两者都得到。我看到的大多数类型都有其非可选版本的类型errres硬编码。

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

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

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

您可以通过更改签名以包含所有可能的值来使这更(好吧,有点)安全。

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

但是没有办法指定两者之间的依赖关系,所以你需要输入 assertres来安静下来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);
    }
  }
})
Run Code Online (Sandbox Code Playgroud)

这还不错,除了:

  • 当您需要重复执行此操作时。
  • 当您不访问某个属性时,您必须键入 assert,这可能意味着您需要导入另一种类型。真的很烦。类型保护将避免这种情况,但是您会受到不必要的运行时损失。
  • 它实际上仍然不安全。它更直接,所以你不得不考虑它,但我们主要依赖于手动断言。

如果你真的想要,你可以用一个类似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
  }
})
Run Code Online (Sandbox Code Playgroud)

老实说,这最终非常好,但这是很多样板,完全违背了常见的节点风格。

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

谢谢!

Rya*_*ugh 6

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

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);
    }
})
Run Code Online (Sandbox Code Playgroud)