-
Notifications
You must be signed in to change notification settings - Fork 12.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Why doesn't awaiting a Promise<never> change reachability? #34955
Comments
I think this makes sense, though two things are unclear to me:
Have you found real cases where you have a |
Consider a As of TS 3.7.3: declare function fetchB(): Promise<string>;
async function logErrorAsync(err: any): Promise<never> {
await fetch("/foo/...");
console.log("Error has been submitted to analytics tool");
throw new Error(err);
}
function logError(err: any): never {
console.error(err);
throw new Error(err);
}
(async () => {
let b: string;
try {
b = await fetchB();
} catch (err) {
await logErrorAsync(err); // awaiting Promise<never>
}
b.toUpperCase(); // "Error: "b" is used before assignment;" would require workarounds
})();
//...
(async () => {
let b: string;
try {
b = await fetchB();
} catch (err) {
logError(err); // returns never
}
b.toUpperCase(); // No error, as expected
})(); |
Yeah, since we actually error on unreachable code, this does seem worthy of the bug label. |
We're closing in on 2 years of this issue, is this something that the TS team wants to do themselves, or could I take this on as a first PR here? |
Backlog = no immediate plans for us to work on it, but Lack of "Help Wanted" = not really sure if there are major roadblocks to a solution. I think this would be a difficult first PR, but you are welcome to try. |
Also this: async function enterToExit(): Promise<never> {
console.log("Press [Enter] to exit");
for await (const event of keypress()) {
if (event.key == "return") {
break;
}
}
Deno.exit();
} |
Here's a real use case for this I ran into today: you want to flush your logs then exit the process e.g. async function exit(exitCode: number): Promise<never> {
await Logger.flush();
process.exit(exitCode);
} |
I believe the code is unreachable because In Java there is the public void errors() throws Exception {
throw new Exception("Exception!");
}
public void test() {
this.errors(); // will error because `errors()` can throw `Exception` and you don't handle the error. Your options are to add `throws Exception` to the signature of `test` or handle the exception with try catch
// unreachable code because possible error not handled
} I believe this (much-needed IMO) feature has been requested in #13219 and until it's implemented we're stuck with |
@williamd5 this works properly without promises: function errors(): never {
throw new Error("Error!")
}
function test() {
errors()
console.log('this is unreachable, and typescript knows it')
} the I have also run into this problem in the wild, specifically when using the general approach i was taking looked like this... class Table<TInsert extends z.ZodSchema> {
// ...
async insert(data: TInsert): Promise<TInsert extends z.ZodNever : never : TInsert> {
db.insert(insertSchema.parse(data))
}
} |
I have a const messenger = new TypedMessenger()
const response = await messenger.send.foo() and However, if it is known in advance that the WebSocket will never respond when |
Does that leave you with never-to-be-fulfilled promises lying around? Or is it internally constructed so that there are no references to them and they can be garbage collected? |
I'm implementing the garbage collection using WeakRefs as we speak :) |
Actually, I just realised what I want to do isn't possible :/ So errors for unreachable code would actually be very convenient here. |
I'm also bumping upon this issue, but was able to work around it using
async function parseFile(): Promise<File> {
try {
return await something()
} catch (cause) {
const errMsg = 'went wrong'
await dbLog(errMsg, { isError: true })
console.error(errMsg, { cause })
throw new Error(errMsg, { cause })
// unreachable code
}
}
async function parseFile(): Promise<File | undefined> {
try {
return await something()
} catch (cause) {
await loggedCriticalError('went wrong', { cause })
// incorrectly assumed reachable code
}
}
async function parseFile(): Promise<File> {
try {
return await something()
} catch (cause) {
// returning an awaited Promise<never> will not add void to the return type of parseFile()
return await loggedCriticalError('went wrong', { cause })
// unreachable code
}
} The need for logging stuff immediately exists because enterprise code is touched by many hands and the error handling in the parent scope might accidentally toss the error message, while fixing that behaviour could introduce its own set of bugs in legacy code that depends on that behaviour. We all know there's no budget for fixing everything. 💸 The inferred return type changed from |
TypeScript Version: 3.7.2
Search Terms:
await
Code
Expected behavior:
As
a1
is inferred to benever
(e.g. behaves like in the non-promised version), I expected the rest of the code to be marked as unreachable aswell:Actual behavior:
The code after the never-returning promise is marked as reachable.
Related Question on StackOverflow: Has more code: https://stackoverflow.com/questions/58732814
Related Issue: #10973 (although marked as "Working as intended", it was changed later. 3.7.2 behaves like the issue opener expected).
If this is not a bug, what is the background for this behavior?
The text was updated successfully, but these errors were encountered: