-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Promise#finally's callback doesn't accept Promise<void> callback, unlike Promise#then #44980
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
Comments
I don’t think it’s really fair to read runtime semantics from the type signature, particularly when
That definition is written that way because it’s actually relevant to the inference for |
@andrewbranch thanks for looking. |
I see. I am saying that the TypeScript typings are correct, because you can’t prove that there’s any problem with them without introducing a third-party tool which introduces its own semantic opinions. TypeScript is, more or less strictly, and among other things, in the business of ensuring that you write code that satisfies type definitions, and functions that return promises clearly satisfy the parameter type for |
This issue has been marked as 'External' and has seen no recent activity. It has been automatically closed for house-keeping purposes. |
@andrewbranch Foreword: In the following paragraphs I'm not trying to be rude or disrespectful. I reviewed my comment and made sure that I'm as respectful as possible. Nevertheless, please forgive me if it seems as if I'm being rude or disrespectful. That is not my intention. My only intention is to engage in an honest discussion with an open mind.
I disagree. I think it's perfectly reasonable to read runtime semantics from the type signature. A type signature should inform the user about what the function does. For example, when I see a function of the type
Again, I disagree. When a higher-order function says that the return type of one of its callbacks is However,
Yes, which is why we should be careful not to abuse it. In my humble opinion,
Just because a program type checks it doesn't imply that the type definitions are correct. For example,
I can only speak for myself, not for the OP, but yes I'm in support of a typing change for purely documentation purposes. Types are meant for documentation just as much as they are meant for ensuring type-safety. TypeScript can't guarantee type-safety because of stuff like
How about the following type which references a new type parameter /**
* Represents the completion of an asynchronous operation
*/
interface Promise<T> {
/**
* Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The
* resolved value cannot be modified from the callback.
* @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).
* @returns A Promise for the completion of the callback.
*/
finally<B>(onfinally?: (() => B | PromiseLike<B>) | undefined | null): Promise<T>
} This is actually the most accurate and honest type for
Forgive me for nitpicking here but I think you meant “(b) code that does error but shouldn't.” Anyway, I agree with you that the current type definition of Nevertheless, although it is correct yet it is neither accurate nor honest. It's not accurate because there is a subtype of
The aforementioned |
Thanks for a well-thought-out response. To be totally honest, I was extra critical of this particular issue because the OP had already posted the same issue in the past and it had been closed as external, and this issue didn’t even mention the original until I followed up. We really can’t have people opening their own duplicates in hopes of eventually getting an answer they like better. This whole discussion ideally should have happened over at #38752. But since you bring up some points worth addressing, here we go.
No, you definitely can’t—all you can be confident about is that the type of the value returned will be assignable to the type of the first parameter. It could be a different value of the same type as the parameter, or it could be a subtype. A lot of your arguments are about human interpretation about how we should interpret types, and programs that type-check, beyond their immediately observable consequences. These are reasonable arguments, for sure, but let’s not call them objectively true as you conclude. But let me address your proposal before getting into matters of opinion:
This is what we call a “useless type parameter” because it doesn’t correlate anything. In fact, a smart linter will complain about this, though in practice it may be fooled by the fact that you’ve used it twice in a single union—you’d have to check. At any rate, the purpose of type parameters is to link two or more parts of the structure of a type. In your earlier example that I nitpicked, you linked the type of the first parameter of a function with its return type. When a type parameter is used in only one part of the structure of a type, it’s useless and should be removed. We would not consider adding this pattern to our library files. Back to opinions and philosophies:
It’s not the length of the type that we care about; it’s more the density. We don’t want any meaningless parts; that is, we don’t want anything that doesn’t participate in type checking. So the question becomes, is there a meaningful and accurate way to write this type while avoiding the pitfalls of |
As the OP, apology for not linking to the original bug #38752. Regarding opening this bug, I didn't think the original bug (#38752) was given close attention that it needed. After it was marked external, I replied and a bot closed it, I didn't see a way to reopen it to continue the conversation. I then emailed the issue to some TypeScript more-expert-than-me colleagues at work and there was consensus on the issue was fair. So I reopened this one without any references to typescript-eslint to keep it focused on TypeScript itself. I agree, making a new bug to continue the discussion isn't good. I would like to request a way to reopen bugs or have a second look at them. Any suggestions? How would I do that in the future? Thanks for reconsidering this topic. |
No, it actually can't be a different value of type You may think that it is possible to narrow the type of const foo = <A, B>(a: A, _b: B): A => {
if (typeof a === 'number') {
// Type 'number' is not assignable to type 'A'.
// 'A' could be instantiated with an arbitrary type which could be unrelated to 'number'.
return 10;
}
if (a instanceof Date) {
// Type 'Date' is not assignable to type 'A'.
// 'A' could be instantiated with an arbitrary type which could be unrelated to 'Date'.
return new Date();
}
return a;
}; The only way we can return a value other than the first parameter is if we disable the type checker by type casting the return value to
I only concluded that the current type definition of Both of these are objective facts. They aren't open to human interpretation. The type
— TypeScript Wiki FAQ: Why are functions returning non-
Actually, it does correlate the two sides of the union. If the left-hand side of the union is However, even if
I disagree. Consider a function of the type
This is a very subjective statement. To a functional programmer like me,
Fair enough. At the end of the day, TypeScript is not my project. However, I would like to state that I see several advantages of using type parameters instead of
On the contrary, if you don't want any meaningless parts then you should use a type parameter instead of Also, there are technical bases for the type
The type
Well, if you use the return type
You know what else is backward compatible? Type parameters. You get all of the benefits of not using
I don't care about external lint rules either, so we're on the same page here. 😄 |
Just comment on the closed bug. We don’t unsubscribe from closed issues, and if there is a reason to reopen them or reengage with them, we do—as is happening here.
I think you’re forgetting that you have ways of making plausibly correct subtypes of objects in JavaScript. It’s true that if you wind up with primitives you’re kind of stuck. https://www.typescriptlang.org/play?#code/MYewdgzgLgBAZiEBGGBeGAeAggGhgIQD4AKAQwC4ZcYAjS-ASkqzUJgG8AoGHmASzgwyMAGQjao8VACeABwCmIQaTSp0AIhA0AVvOBR1kmDIVKJajVt371DDt16OATvKgBXJ2BgB5HXqgAdKQQEHwA5mDE7AC+eDR4pAwA3A480akwLu6eMKQp6UA You can also do some arguably correct things with user-defined type guards, which are not checked for accuracy, but are not wrong and are less suspicious than casting to I guess we’ll have to agree to disagree about what type parameters are for. I won’t be monitoring this thread anymore as the discussion has been exhausted of anything useful to resolving the original issue. |
Allow promises in finally per discussion in microsoft#44980
@andrewbranch are there any concrete downsides to having There are plenty of other builtin lib defs for callbacks that return interface TransformerFlushCallback<O> {
(controller: TransformStreamDefaultController<O>): void | PromiseLike<void>;
} If there were a way in TS types to declare that a callback whose return value is ignored must be synchronous then ESLint could be smarter about this rule, but there's no way to declare that in TS types AFAIK. |
lib Update Request
Configuration Check
My compilation target is
ES2018
and my lib isthe default
.Missing / Incorrect Definition
Promise#finally
Sample Code
The suggested change is changing
lib/lib.es2018.promise.d.ts
fromto
The current typing, without the PromiseLike, suggests that the runtime will not wait for the onfinally callback to resolve if it's a Promise, however, it does. This can be seen in the console when
test
changes frompending
to123
.Additionally, for consistency between
#then
and#finally
, the currentonfinally
definition doesn't matchPromise#then
inlib/lib.es5.d.ts
whereonfullfilled
returnsTResult1 | PromiseLike<TResult1>
.Documentation Link
https://tc39.es/ecma262/#sec-promise.prototype.finally
Suggests that
.finally()
is implemented similar to.then(result => Promise.resolve(onFinally()).then(result))
.The text was updated successfully, but these errors were encountered: