-
Notifications
You must be signed in to change notification settings - Fork 463
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
Safe promises #5709
Safe promises #5709
Conversation
6e15b9a
to
51f5c51
Compare
51f5c51
to
f3c400b
Compare
@@ -249,6 +249,9 @@ module Re = Js_re | |||
module Promise = Js_promise | |||
(** Provide bindings to JS Promise *) | |||
|
|||
module Promise2 = Js_promise2 | |||
(** Provide bindings to JS Promise *) | |||
|
|||
module Date = Js_date | |||
(** Provide bindings for JS Date *) | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO: integrate orthogonal quality of life improvements from https://github.com/ryyppy/rescript-promise
TODO2: plan the whole Js.Foo2 story and how it should move to be the default instead. This cleanup could go into v11. For a separate issue.
Is this really planned for 10.1? Or should it go into 11.0? |
This is 10.1 material: it's where both async/await and the warning for nested promises were introduced. Based on current understanding, there's no basis for introducing that warning. |
1c3421a
to
14cc081
Compare
aa012f8
to
eee40b1
Compare
Not clear how much of rescript-promise should go here. |
Introduce safe promises, based on #5707 - Add t-first Js.Promise2 with safe bindings - Remove type check for nested promises - Add example illustrating a typical example of nested promises, and how it goes away with Js.Promise2
6916ba5
to
52e7cef
Compare
/** Type-safe t-first then */ | ||
let then: (promise<'a>, 'a => promise<'b>) => promise<'b> = %raw(` | ||
function(p, cont) { | ||
Promise.resolve(p).then(cont) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you wrap a promise with Promise.resolve
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because promises are flattened at runtime and where you expect to have, say promise<int>
because you looked inside promise<promise<int>>
what you actually get is an int
. When that happens, the int
is converted back to the promise<int>
you were expecting, so you can call .then
on it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because promises are flattened at runtime and where you expect to have, say promise because you looked inside promise<promise> what you actually get is an int.
Once it became a promise it will stay a promise. So if we have promise<promise<int>>
in runtime, it'll be flattened to promise<int>
(not int
). So we can call .then
without additional Promise.resolve
.
At least it works this way in JavaScript. Maybe we're talking about different things.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not easy to explain in few words. But you can check the example: https://github.com/rescript-lang/rescript-compiler/pull/5709/files#diff-dfcc91a2765ccd42f814d348224f3bc20b343386851e14fd99132000aa56fd78R13
It is flattened to promise<int>
, but when you "look inside" i.e. await it, you get int
. (Or you could look inside by doing another .then
).
Another way to answer: in JS await
is happy to receive a value such as a number, it does not need a promise. This was done intentionally. This change does the same for then
: it makes then
happy to receive a value such as a number.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see. But I'm not sure that the problem should be solved by Promise bindings. It's more of a types problem, not a runtime one.
For example, if xxx
has the type promise<promise<promise<int>>>
, the Promise2
will also stop working correctly. I think it's fine to provide some helper function to flatten promises eg Promise2.flatten, and leave everything to the user's responsibility.
This code covers an edge-case while going against the runtime-free Js
module nature. It tries to keep the correct behavior. That's definitely good, but I'm not sure whether it's worth it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I suggest is:
let nestedPromise = async (xxx: promise<int>) => { let xx = await xxx Js.log2("Promise2.then", xx) } module Promise2 = { external flatten: promise<promise<'a>> => promise<'a> = "%identity" } let someFnThatReturnsNestedPromises: unit => promise<promise<promise<int>>> = () => %raw("Promise.resolve(123)") someFnThatReturnsNestedPromises() ->Promise2.flatten ->Promise2.flatten ->nestedPromise ->ignore
This way, we solve the problem immediately as it's raised. No twisting of the
promise
type meaning. Also, thenestedPromise
code became much cleaner.
I think there's some confusion on what the problem is.
The problem is that you can inadvertently write some code that uses nested promises and crash.
The nested promise type can easily be in the middle of an expression, and the user might never see it written down explicitly.
If you already know there are nested promises, then clearly there's no problem and you can flatten them explicitly.
The problem this is trying to solve is when you don't realise you're using nested promises.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I consider crashing a healthy thing when it happens during development. I think it's where we derail in our opinions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Especially when using Js
module. Maybe it's for Belt
to handle this problem. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely, wanting those examples to crash is one possible opinion.
What is surprising, at least to me, is that the same example written only in terms of async/await
does not crash.
This boils down to await 3
not crashing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely, wanting those examples to crash is one possible opinion.
It's how it used to work https://github.com/rescript-lang/rescript-compiler/pull/5709/files#diff-dfcc91a2765ccd42f814d348224f3bc20b343386851e14fd99132000aa56fd78R13
Ideally would be nice to have types that can automatically flatten themselves eg, promise<promise<t>>
automatically becomes promise<t>
on the type system level. The same thing for the option
type and others. But I can imagine that it's not easy to do.
Well, I don't have anything else to add. I'm more into leaving things as they are right now. Both approaches have pros and cons. I'm not a decision-maker here.
Introduce safe promises, based on #5707