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

时间:2016-09-30 22:39:12

标签: typescript

节点回调类似于:

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

因此,回调将获得errres,但不会同时获得。我看到的大部分内容都将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);
    }
  }
})

除了:

之外,这并不算太糟糕
  • 当您需要反复进行时。
  • 如果您没有访问某个属性,那么 键入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
  }
})

老实说,这真的很不错,但这是很多样板,完全违背了常见的节点风格。

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

谢谢!

1 个答案:

答案 0 :(得分:4)

除了你所做的以外,我见过的唯一好的模式看起来像这样:

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);
    }
})