It is difficult to read when too much characters in the method chaining,
"@" can split them and be highlighted by code editor to group the asynchronous functions.
Other benefit
To protect our fingers and keyboard
With "@" before
let task = await getAsyncTask();
We need to press "a" key -> "w" key -> "a" key -> "i" key -> "t" key -> Space key = pressing key 6 times.
If you don't want to wait it, you need to move back to "await" and delete it.
With "@" after
let task = getAsyncTask()@;
Only press "2" key + Shift key = pressing key once. (It can be auto completed by code editor.)
If you don't want to wait it, you only need to press Backspace key once.
How to try it out
If you have VS Code, you can install this demo extension to try it out.
I actually like the weight of the await keyword, and it's position as a prefix, as it represents an interleaving/suspension point that shouldn't be glossed over.
The nesting readability concern is greatly reduced with pipelines, and await is one of the motivating use cases (and one of the reason why hack pipes were favored).
The wavy dot proposal is actually a lot more complex than simply awaiting on intermediate results as it allows pipelining. It's likely that a combination of pipeline and runtime helpers would sufficiently reduce the need for wavy dot, which is a reason why we haven't pushed it further for now.
@aclaymore's referenced proposal would directly solve this issue, and it would be nice to see something like that added. As for what can be done today, I've started making it a habit to just use .then() instead of parentheses whenever I need to nest awaits. It generally makes the result a little cleaner.
Some examples:
// before
await (await fetch('https://example.com')).text()
// after
await fetch('https://example.com').then(x => x.text())
// before
await (await (await (await asyncCalculator.plusAsync(1)).minusAsync(2)).multiplyAsync(3)).divideAsync(4);
// after
await asyncCalculator.plusAsync(1)
.then(x => x.minusAsync(2))
.then(x => x.multiplyAsync(3))
.then(x => x.divideAsync(4))
Also, for this point:
To protect our fingers and keyboard
In general, it's better to optimize for readability over writability, as code is read much more often than it is written. So, while this argument is still a valid one, it's generally put really low on the list of priorities. It's often overshadowed by things like: how readable is this new operator over the old one? (which, I'd argue if we're not nesting, the await operator is more readable, but that's really a matter of taste). Do we really need to use one of our few precious ascii symbols for this? If we introduce too many operators like this, will JavaScript just turn into an ascii soup that's difficult to understand? etc.
Edit: Just saw @mhofman's comment. Nested awaits have always been a bit of a pain point for me. When I started using the .then() trick, it helped out a lot. I didn't think about how pipelines would fix this, but yeah, I guess that would help fix the issue a lot - to the point where I wouldn't really feel a desire for this sort of feature anymore. But, I guess pipelines aren't that much different from just using .then() as well, which might be why I found that to be so helpful.
@tzengshinfu - careful there. It's generally recommended not to modify the prototypes of built-in objects, and that's done for good reason - you might add a function that conflicts with current or future JavaScript functions, or that conflicts with other libraries. (Every time JavaScript tries to add a function, they generally have to look around to make sure their function doesn't cause issues with any libraries that have modified built-in prototypes.
In this case, your library actually conflicts with an existing protocol - the "thenable" protocol. Promises will chain with anything that has a then function
This means:
> Date.prototype.then = () => console.log('hi there')
> await Promise.resolve().then(() => new Date())
hi there
(script never terminates)
Or, if I run this line of code after loading your library in, the promise will never resolve and the script will never terminate.
> console.log(await Promise.resolve().then(() => new Date()))
(script never terminates)
Part of the issue here is the fact that that ES6 chose to make anything with a then() function implement this protocol (as opposed to using symbols), which can cause surprising effects to anyone who adds a "then()" function to their objects. But, the fact that you're modifying prototypes means existing code that tries to return a Date object or what-not from a promise will break, which makes things even worse.
You could just fix this by renaming the function, but my personal recommendation would be to do something that you might not like, because it's a bit more verbose. (This is how lodash solves the problem).
import chain from 'your-library'
return chain(2 + 2) // Returns an object that holds the internal value, and has a .then() function
.then(x => x + 1) // Or call it next() or something if you want this to not be then-able.
.then(x => x * 2)
.value() // Gets the internal value out of this wrapper object
Hi, @theScottyJam ,
Thank you for your explaining, I learned a lot from you again.
Now I got it, Thenable protocol, Duck typing, and don't pollute the prototypes of built-in objects.
I will build a wrapper object to chain it's mehods.