节点回调如下:
interface NodeCallback<TResult,TError> {
(err: TError): void;
(err: null, res: TResult): void;
}
所以回调函数要么得到err
要么得到res
,但不能两者都得到。我看到的大多数类型都将err
和res
的类型硬编码为它们的非可选版本。
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'.
}
})