Promise chaining operator

Current Situation / Problem

Promises have been a great feature to allow for asynchronous operations while avoiding callback hell by allowing chaining through .then(). After that, async/await have been introduced to once again make code more readable by making it so that asynchronous code looks pretty much like regular code (plus the await operator, of course).

However, one area that neither .then() nor await solve to an acceptable degree is property access chaining. Suppose we have synchronous code like this:

one().two().three()

This looks very nice and is well readable. It does however not work if these functions are asynchronous and return a Promise. In that case, chaining would look like one of the following lines:

one().then(result => result.two()).then(result => result.three())
// or
(await (await one()).two()).three()

Both of these solutions are less than ideal. The first solution is more than three times longer because of all the .then() calls. The second solution is shorter (although still longer then synchronous chaining), but uses a lot of nesting (the leftmost await belongs to the call of .two()).

The nesting can be somewhat circumvented by using the proposed pipeline operator, but this is also not the shortest or most readable solution:

await one() |> await %.two() |> %.three()

The more practical solution is to simply put every asynchronous call on a single line, but this requires a separate single-use variable and a lot of vertical space:

const oneResult = await one();
const twoResult = await oneResult.two();
twoResult.three();

Proposal

Basic Concept

To solve this problem, I would propose a new operator (-> will be used as a placeholder for this operator for now). This operator does two things. It awaits the Promise and then serves as property access (dot operator). This operator would allow the functions from above to be chained like this:

one()->two()->three()

And would be functionally equivalent to this example from above:

one().then(result => result.two()).then(result => result.three())
// or
(await (await one()).two()).three()

Such an operator would be very convenient in cases where data is fetched asynchronously (e.g. from a web server or a database) and then further processed (e.g. using array .map()). For example, fetching to-dos and making their titles uppercase would look like this:

fetch('https://jsonplaceholder.typicode.com/todos')->json()->map(todo => ({ ...todo, title: todo.title.toUpperCase() }))

Arrow Rationale

Using an arrow as the operator (->) was inspired by C++ which uses the same operator for dereferencing and property access (which is comparable to this use case of awaiting and property access). It also very nicely denotes the order in which the properties are being accessed.

Optional Chaining & String Property Access

The operator would also support optional chaining and string property access:

// optional chaining
one()?->two()

// string property access
one()->['two']

Usage with non-Thennable values

An open question would be how the operator behaves in cases where the object that it is called on is not Thennable, e.g. 'Hello World'->toString(). So far, I see can see two options:

  1. The call fails (just like calling .then() on a non-Thennable object would fail). This has the disadvantage that it makes values with the type T | Promise<T> difficult to use because it is not known whether the operator will fail or not. This would also make the operator easier to implement by transpilers such as Babel or TypeScript, because they soley need to replace the -> operator with the respective .then() call.
  2. The value will be wrapped into a promise if it is not Thennable (similarly to an async function does when a non-Promise is returned, or await does when awaiting a non-Promise value). This makes the operator easier to use, but I do not know whether this has any runtime implications in regard to performance. This option seems to be more convenient and should be preferred if it does not kill performance.

https://github.com/tc39/proposal-eventual-send

Additionally, please see previous discussions on this topic: