-
Notifications
You must be signed in to change notification settings - Fork 107
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
Current suggested async
syntax feels *very* confusing
#83
Comments
Right, #66 ended with the conclusion that (The overall problem is that plain "calling" of a function on a value isn't always sufficient, as the value is sometimes inside of a data structure and you instead want to instead map into it and extract it from that, to return it to being a plain value. This extends further than just Promises; those just happen to be the only copointed functor we've blessed with language syntax. ^_^ In other words, there's a more generic solution waiting to be produced here.) |
@tabatkins For study, can you provide examples where this syntax causes problems? |
I agree. Differentiating semantics based on the presence of parentheses is going to be a clear compositionality hazard. |
In an expression like
If I don't know about this new feature but do know how What this syntax is really trying to do is produce a new operator, the (I'm fine with |
I agree. You can find my dissent in the issues @mAAdhaTTah linked to above. I think that tacking async onto this solid proposal is a bad route in general. You make a good point that it's an attempt to introduce a new operator. I would rather that this new async/pipeline operator be added in a separate proposal, after this traditional pipeline operator is successfully added. |
My understanding is the committee is not amenable to this approach.
This was my original suggestion, but we ran into spec problems with "dangling await": "foo" |> bar |> baz |> await was difficult to spec, although I can't remember the details there. @littledan raised those concerns. |
To further summarize:
|
It looks like it'll get relitigated at this upcoming meeting - there was a lot more grumbling about it at our pre-meeting today. If dangling-await is truly problematic for the |
- Pipeline operator: There are some significant, recently filed issues with the proposal that need to be resolved before Stage 2 including tc39/proposal-pipeline-operator#82 and tc39/proposal-pipeline-operator#83 (as well as the arrow function grammar). I plan to follow up on these offline. - Extensible literals: Changes to the proposal to incorporate feedback from the last presentation are still needed, but it's too close to the meeting to let others review these changes.
It seems to me that we might be able to avoid the syntax difficulties by implementing the desired Let's say that we introduce a special function-like production that can appear only after
The semantics would be to simply The required parenthesis would allow us to avoid the dangling-await problem with This would work with regular method chaining as well: new DBConnection(connectionString)
.open()
::await()
.query(sql)
::await();
::displayQueryResults(); |
So would this: "foo" |> bar |> baz |> await become: "foo" |> bar |> baz::await() or maybe even without the function-like production: "foo" |> bar |> baz::await ? |
Ah, it looks like there would be a precedence issue when trying to combine the two grammars together. As it stands, you'd have to parenthesize, which isn't what you want: ("foo" |> bar |> baz)::await() |
FYI I took optional chaining off of the January agenda so we can work more on this offline before proposing for Stage 2. |
The Would the following make the
Use case examples: // Good (#4)
fetch(opts) |> await |> handle
// Good (#3)
x |> (await getAsyncFunc)
// Syntax Error, due to point #2
x |> await getAsyncFunc
// Syntax Error obviously, but for the same reason as above
f() g()
// Good (#2)
fetch(opts) |> await // <-- No ASI hazard
g() |
For what it's worth, var await = 1, foo = 2, bar = 3;
foo |await> bar; // parses as (foo | (await > bar)) |
@not-an-aardvark only outside of an async function, whereas presumably |
The particular problem with "dangling await" is that it's a new ASI hazard. If you had something like this: let x = a |> f |> await
g(x) This would actually be awaiting
|
Option 3 or 4 is fine with me. I dislike |
I was under the impression that a |> f // f(a)
a |> await f // (await f)(a)
a |await> f // await f(a)
a |await> await f // await (await f)(a) Wouldn't this syntax then dispel the ASI hazards while also allowing us to keep the unambiguity of evaluating A problem I'm noticing now, however, is that this is antithetical to the idea of reading left-to-right |
|
The ultimate problem here is that |
I like option 3. Avoid the ASI hazard with some extra syntax. |
Is there a difference between |
@not-an-aardvark Do you mean |
I also think banning |
Frankly, I’m not a huge fan of the Would it help if we required partial application syntax when using
I would try not to ban |
@mariusschulz What did you think of the |
@littledan I think it looks clean and reads very nicely. I’d be happy with this syntax as well, despite the potential ASI hazard. |
I disagree with the idea that this proposal should not be concerned with async functions. I think that await should definitely be included in this proposal. My favorite syntax this far is: a
|> fetch
|> await
|> json
|> await
|> action I think it makes much more sense than |
Hey all, I meant for my bullet points to be treated as a single, collective solution. In other words, the solution involves enforcing all 5 points. It's intended to provide |
Can you clarify what you're referring to? I'm having a hard time finding the bullet points you're referring to. |
@gilbert Thanks for the clarification; now that I re-read it, I think we're converging on your proposal. I'll try to write it up in spec-ese within a few weeks. @pitaj I think @gilbert is referring to this comment: #83 (comment) |
@littledan Ah, thank you. In that case, then yes I agree with @gilbert 's solution |
Guys, there are more generic cases than just
Actually |
@Alexsey Your examples would be written like this with the proposal by @gilbert // these don't even need any pipeline await syntax
(await f)(a) === a |> (await f)
(await obj.f())(a) === a |> (await obj.f())
(await obj.f()).g(a) === a |> (await obj.f()).g
// this one is the only one where it's useful
await (await obj.f()).g(a) === a |> (await obj.f()).g |> await This is actually simpler than what you have written. In fact, what you have written makes no sense. You examples all involve the results of It's not about |
What about simply introducing a new "asynchronous pipeline" or "await pipeline" operator that is semantically equivalent to the proposed I would propose adopting an operator that looks similar to the synchronous pipeline operator, but that could be interpreted to visually suggests asynchronicity or delay? Something such as So instead of "foo" |> bar |> await |> baz; or "foo" |> bar |await> baz; We could have one of these (Or something similar. Any other ideas?) "foo" |> bar ||> baz;
"foo" |> bar ]> baz;
"foo" |> bar }> baz;
"foo" |> bar :> baz;
"foo" |> bar |:> baz; |
Since this would also affect |
I've had thoughts around this too. How do people feel about this idea. I think the problem with pipe(person.score
double,
_ => add(7, _),
_ => boundScore(0, 100, _)); Conceptually I think the most intuitive async version of this would be to model it after an async function and have pipe return a Promise. I'm guessing this is how most of us would implement this. Each step would automatically be awaited if you returned a Promise. For example: // We await the pipeline as a whole since it returns a Promise
await pipeAsync(person.score
double,
_ => add(7, _),
_ => isGlobalHighScoreP(_), // Returns Promise, will be awaited before next step
_ => _ ? "You are top" : "Try again"); Do people agree that this is intuitive behaviour? If others think this is intuitive I would suggest we find a syntax that indicates the entire pipeline is async. One immediate idea is to use the async keyword but I don't want to dwell on syntax yet until we determine what the intuitive behaviour is. I think this is why a few people are stumbling at the current syntax because it is like you are doing the following, which I don't think is intuitive: // (NOT what I am proposing)
pipe(person.score
_ => isGlobalHighScoreP(_),
await, // awaiting being passed to function is not intuitive
_ => _ ? "You are top" : "Try again"); |
Who? In a comment on this proposal, or somewhere else? |
Would an implicitly awaited promise be a promise for the value, or a promise for the function, both? |
To me, await being used like a function which unwraps a Promise into the value it resolves to, in the context of an async function, is totally intuitive. Frankly, I don't understand why any of you have a problem with the This syntax would only be possible within async functions, it does not apply generally to dealing with promises. If you havea bunch of functions built to deal with promises, then you can use them in a normal pipeline. |
I am imagining It would execute the function and await the value of the returned Promise. i.e. something like async function pipeAsync (val, ...fns) {
val = await val;
for (const fn of fns) {
val = await fn(val);
}
return val;
}
It was the overall impression I got following the threads in the issues. Maybe I am mistaken? Do others feel that |
Some background conversation in #66.
Otherwise, @pitaj's comment is a good summary of why I like I'll also note that when this issue was raised, the syntax was: x |> await foo |> bar which is confusing. I might actually suggest we close this issue, as the original question no longer applies to any of the current proposals. |
Resurrecting this thread in light of a conversation with @tabatkins and @jridgewell during the last TC39 meeting. In general we were discussing both
Thus, I propose the following:
The other reason this symmetry is useful with respect to x |> yield a |> F Your intention might have been for the following to happen: (x |> yield a) |> F However, due to the precedence of the x |> yield (a |> F) By forcing the parenthesis for inline In summary (for F#-style pipes):
|
In F# syntax, yes, all this is good and exactly what I would want and expect. (In Hack syntax, |
Closing since this was my own issue, and it's all solved now. ^_^ |
Per the slides for the upcoming tc39 meeting, the proposed syntax for handling async functions in a pipeline is:
Which, assuming
baz
is an async function, will await it's return value before chaining toqux
.The slides explicitly call out that
(await baz)
would do something completely different - it would await the baz value (assumed to be a promise) then pipeline to the function it resolves to.This seems incredibly confusing! I don't have a great suggestion to sugar this case instead, but this particular approach seems v bad. Adding parentheses shouldn't change behavior beyond just forcing operator precedence.
The text was updated successfully, but these errors were encountered: