Recently, I encountered some strange code that was compiling fine even though it had some issues. After fixing it, I still couldn't figure out why it was originally working:
const stringOrNull: () => Promise<string | null> = () =>
Promise.resolve(Math.random() < 0.5 ? "Hello, world." : null);
// The mysterious code block
const stringOrNullWeird: () => Promise<string> = () =>
new Promise(resolve =>
stringOrNull().then(result => resolve(result ? result : undefined))
);
// This will potentially cause a crash with an UnhandledPromiseRejectionWarning
[...Array(10).keys()].forEach(async () =>
console.log((await stringOrNullWeird()).toLocaleLowerCase())
);
To understand this oddity, I modified the function and stored the returned Promise
in a temporary variable to check TypeScript's inferred type. Surprisingly, it turned out to be unknown
, leading to compilation errors.
// This doesn't compile as expected
// Type '() => Promise<unknown>' is not assignable to type '() => Promise<string>'.
// Type 'Promise<unknown>' is not assignable to type 'Promise<string>'.
// Type 'unknown' is not assignable to type 'string'.ts(2322)
const stringOrNullUnknown: () => Promise<string> = () => {
// const p: Promise<unknown>
const p = new Promise(resolve =>
stringOrNull().then(result => resolve(result ? result : undefined))
);
return p;
};
This discrepancy between the two forms left me puzzled about why the initial version compiled successfully, compromising type safety?