Node callbacks look something like:
interface NodeCallback<TResult,TError> {
(err: TError): void;
(err: null, res: TResult): void;
}
So the callback will either get err or res but not both. Most of the typings I see have the types of err and res hard coded to their non-optional versions.
function readdir(path: string, callback?: (err: NodeJS.ErrnoException, files: string[]) => void): void;
This isn't strictly typesafe. For example this compiles fine:
fs.readdir('/', (err, files) => {
if (err !== null) { // There's an error!
files.forEach(log); // Still using the result just fine.
}
})
You can make this more (well, kind of) safe by changing the signature to include all possible values.
function readdir(path: string, callback?: (err: null | NodeJS.ErrnoException, files?: string[]) => void): void;
But there's no way to specify the dependency between the two so you need to type assert res to quiet down 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);
}
}
})
This is not too bad except for:
If you really wanted to you could do this with a Result-like discriminated union:
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
}
})
Which ends up being pretty nice honestly, but that's a lot of boilerplate and totally goes against common node style.
So my question is does typescript currently have a way to make this super common pattern both typesafe and convenient? I'm pretty sure the answer is no right now, and that's not a big deal, but I was just curious.
Thanks!
The only good pattern I've seen, other than what you've done, looks like this:
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);
}
})
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With