jdanyow / aurelia-async

A plugin for the Aurelia platform providing asynchronous binding support
MIT License
27 stars 4 forks source link

[Discussion] Syntax is terrible #3

Open jods4 opened 8 years ago

jods4 commented 8 years ago

This is something that Aurelia absolutely needs, but I think that the double dotted syntax is aweful. It conveys no meaning. And when I just want to use a Promised value I don't see why I should .value it.

Given the current work on binding behaviors, I think it would be more appropriate to implement this through a behavior: value.bind="promisedName & async".

Other options could be handled in a similar fashion if it is wanted (say using & async-value, & async-status etc.).

That said, it doesn't compose well... What about: value.bind="user.address.city & async". What part of the path does async apply to? :frowning:

Going a little bit further, I almost think that this should be baked in into Aurelia. I mean: what use is it to bind a Promise? Other than wanting to display its value when it resolves? So doing value.bind="user.firstName" could "just work". Of course, this special-cases one object (Promise), but it is a core platform object with very special meaning and purpose, so I think that's OK.

jdanyow commented 8 years ago

@jods4 - thanks for your feedback. I'm not 100% in love with the syntax either. I'll try to respond to some of your comments inline below:


This is something that Aurelia absolutely needs, but I think that the double dotted syntax is aweful. It conveys no meaning. And when I just want to use a Promised value I don't see why I should .value it.

Given the current work on binding behaviors, I think it would be more appropriate to implement this through a behavior: value.bind="promisedName & async".

We are still planning on implementing the async binding behavior. I actually worked on the initial prototype for binding behaviors.

Other options could be handled in a similar fashion if it is wanted (say using & async-value, & async-status etc.).

:+1: good call

That said, it doesn't compose well... What about: value.bind="user.address.city & async". What part of the path does async apply to? :frowning:

You nailed it. This was the major motivator for integrating async binding more deeply in the parser.

Going a little bit further, I almost think that this should be baked in into Aurelia. I mean: what use is it to bind a Promise? Other than wanting to display its value when it resolves? So doing value.bind="user.firstName" could "just work". Of course, this special-cases one object (Promise), but it is a core platform object with very special meaning and purpose, so I think that's OK.

I think you have a solid argument here. That said, there are serious problems with this approach:

  1. There are many types of Promises: native promises, bluebird promises, Q promises, SystemJS polyfills, etc, etc. To cover all these scenarios we'd be reduced to checking whether an object is "promise like" (has a .then method) at expression-evaluation-time (vs once at parse-time). This could lead to a lot of confusion when we inevitably misidentify an object as a "Promise".
  2. Forcing async binding on all "promise like" or "Rx Observable like" objects makes it impossible to bind to their synchronous member properties/methods.
  3. Performance: the current (explicit) implementation incurs a negligible cost at expression-parse-time when checking for the extra period to identify an async binding expression. Checking for "promise like" or "observable like" objects at expression-evaluation-time would be very costly and difficult to implement correctly. This overhead would impact all types of binding expressions.
  4. Making binding expressions mean different things depending on the context/value is generally confusing for developers and could have a negative impact on tooling implementations down the road.

Again, thanks for your feedback, hopefully my responses are helpful in framing the task at hand and lead to a better implementation. Let me know what you think.

jods4 commented 8 years ago

Yeah some of those remarks occured to me as well. I'm not 100% decided if Promises should get a first-class citizen treatment or they are just a built-in binding behaviour.

First off let me say that it's clearly not a question of verbosity and I am fine with slapping & async on some bindings. After all many won't be promises and this is not that verbose at all.

Some more thinking about your points:

  1. Many types of promises. True and I thought about it. My thinking was: after all we're going "standard" and Aurelia is already making use of a lot of Promises, both internally and in its public API. So give special treatment to window.Promise and that's it. Downside: people who use libraries for "better" Promises (let's face it the standard API is quite limited) would need to make sure their library is set to the window.Promise (which is not automatic for some library, e.g. Bluebird when it detects and AMD loader) or that the library properly extends the existing window.Promise. Downside: people may use jQuery for http requests, e.g. $.get('/some-resource'). Let's skip the "this is not good" part and just say that the resulting jQuery promise-like object will not be bindable with our system. Downside: this only works with Promises, e.g. not RX. Well... I think that Promises are special. They are the standard async unit of work. RX is an outside library and not everyone think it's great (no arguing here, just sayin'). So I think RX should be an external "opt-in" thing and not core anyway.
  2. We can't access synchronous members. Let's face it: the HTML5 definition of Promise has no member. There are two interesting things to do when binding a promise: (a) use its value when available (which could be the default) and (b) transform it into a boolean "resolved" flag, which could be exposed by a more specific means such as a behavior: <div class='loading' if.bind='p & async-resolved'>. I note here that the "is resolved" aspect is always at the end of the path, so there is no question as to which part is a Promise in: if.bind='user.password.isAvailable & async-resolved.
  3. Yes, at every update there would be a check if (value instanceof Promise). This isn't very expensive but it would be paid at each update-target. I guess benchmarking would say if this is acceptable or not.
  4. I generally agree but maybe not in this case. E.g. lots of API in Aurelia already accept a value or a Promise for a value. Other example: ASP.NET has extensive support for async controllers if you choose to return a Task. Asynchronicity is a key part of the platform and I think special support for it is nice.

That said explicit & async has some advantages such as no overhead when it's not used. And being explicit may prevent some nasty, unexpected interactions with other features. But we need to find a solution to the path problem.

Idea 1: Would it be possible to parenthesize binding behaviors (and maybe converters for consistency)? Something like: ${ (user & async).age | format }? It seems to me that the main problem (beside added complexity) is ambiguity with JS expression.

Idea 2: Would it be bad if the async modifier was applied to the whole chain? That in ${ user.age & async } every part of the path could return a Promise or not and that the result still works as it should? Not sure how doable that is but that would be a good looking solution. And then I change my mind about the async-resolved I mentioned above. ${ user.child & async-resolved } could just as well support several Promises in the path (user AND child might each be promises and the result is only true when the whole chain has resolved).

ewinslow commented 8 years ago

I think it helps to think about what the equivalent expression would be in plain JS.

If you are going to set textContent of an element to an async-resolved value, you'd use

async function render() {
  el.textContent = await somePromise();
}

and not:

function render() {
  el.textContent = somePromise();
}

So, count me in favor of some kind of syntax that indicates you're working with a promise that you want to unwrap, rather than automatic unwrapping. And +1 for await as the keyword for alignment with ES7. :)

ewinslow commented 8 years ago

For the same reason, I prefer Idea 1 as a solution to the path problem you've described. Here is the plain-js solution:

async function render() {
  el.textContent = (await getUser()).name;
}

Syntax that mimics/resembles this approach would be the most intuitive to devs, I suspect.

EisenbergEffect commented 8 years ago

I'll let you guys continue to discuss, but my one comment is that I do think there should be an explicit syntax to indicate what is happening. Whether or not that is in the expression or the binding behavior, you guys can keep discussing. But, I think, esp. based on some of my experience trying to make this work in Durandal as well as some experience of the Angular team, without something explicit, it can be confusing...and possibly affect performance in a broad way.

jods4 commented 8 years ago

First off I'd like to say that I think this is not an easy design issue and I'm kind of thinking aloud here. That's why you may sometimes have the impression that I'm playing devil's advocate or changing my mind erratically.

I agree that although async is important, it is not the most common scenario and having to pay a perf price for checking whether each part of each bound path is a Promise is probably not a great idea. Being explicit also serves as documentation for other devs, and some clarity about what is going on is good.

The next question is: supported by Aurelia's binding DSL or a binding behavior? I currently prefer the behavior approach because:

As is probably apparent, I'm currently leaning towards an external syntax with a binding behavior, maybe <div textcontent.bind='user.address.street & async'>.

To be fair, let's answers the questions I raised for await above with async behavior:

What do you think?

jwahyoung commented 8 years ago

@EisenbergEffect I also support the explicit syntax for Promises. While it would be nice to have it implicitly, I'd rather opt-in than opt-out.

@jods4 I definitely like the "binding to the promise state" behavior parameters, but the basic promise object doesn't actually expose these, does it? A basic promise is an object that exposes a "then" method. Everything else is just sugar. It's nice, but I don't think it's something that the library could or should rely upon.

Further - although I'm not a fan of the dot-dot syntax either - I do like the fact that one can explicitly state where the promise is in the chain - for instance, company..group.members vs company.group..members, etc. (The only issue that I see here is that it's a bit brittle - if the location of the promise changes or is removed, the view would have to be updated - but that's a small cost, as it's akin to changing a bound variable name or something.)

Because promises are such a large part of Javascript, I'm of the mindset that the binding system should handle this (similar to Ruby's null handling with property accessors: ex. (object.property?.subprop). I think @jdanyow has the right idea, just the wrong syntax. (That said, I don't want this to turn into Perl soup like Angular 2.)

jods4 commented 8 years ago

I definitely like the "binding to the promise state" behavior parameters, but the basic promise object doesn't actually expose these, does it? A basic promise is an object that exposes a "then" method. Everything else is just sugar. It's nice, but I don't think it's something that the library could or should rely upon.

The library can very easily build this on top of the basic .then() primitive. This is very much what any user of the code would do if they need this feature anyway, so that's a very nice sugar to have.

And this is something that might be very commonly required, e.g. if you display loading indicators on screen when some async operations are in progres (e.g. an ajax request).

I do like the fact that one can explicitly state where the promise is in the chain

What is the benefit of doing that? Or what drawback do you dislike if Aurelia 'just did the right thing'?

jdanyow commented 8 years ago

All, I'd like to move forward on improving the syntax. So far we know one thing: no-one, including me, likes the .. syntax.

The requirements are:

  1. ability to bind to the eventual value(s) of a Promise, Rx Observable and potentially support other async patterns.
  2. ability to bind to the "ready" / "resolved" status of the Promise or Rx Observable.

I'd also like to put to rest talk about "just do the right thing". We are not going to add logic to search for Promise and Observable instances throughout binding expressions. There are several reasons for this:

  1. Performance.
  2. Aesthetics: explicit is better. Think about the await keyword- it doesn't "find the promise" in an expression, it's passed an expression that evaluates to a promise.
  3. Level of effort to implement is too high. Would need to touch almost all the AST nodes.

Ideas: -I don't have an alternate syntax to propose right now. Let me know if you do. If we didn't need the ability to bind to a Promise's "resolved" status we could do something like this: ${await myPromise} and ${(await myPromise).foo.bar.baz} -Should we handle Rx separately?

Thoughts?

jwahyoung commented 8 years ago

I'd been giving this some thought in the past weeks. The syntax suggested by @jods4 has grown on me a bit, but I'm still not sure that it should be a binding behavior. The pros of the binding behavior approach, though, is the ability to easily adapt to other asynchronous bindings, such as Rx. Having something pluggable means that it can be extended easily (to work with native promises, Bluebird promise features, Rx, etc) and in a consistent manner.

It might be good to open a separate github issue for Rx Observables. I haven't looked at the library very much, but it might be good to see what they expose. Based on what I've read, Rx Observables expose a lot of functionality; I think it'd be good to see where promises and Rx Observables overlap (and attempt to establish possible usage patterns for Rx and make sure that we can easily support them down the line).

At the moment, I don't have a proposal for the value syntax itself; many of the possible inline symbols are Javascript control characters, and those that aren't could be part of a variable or property name. (I'd thought about using the question mark (?), but that's part of the ternary operator; the asterisk ('*') is now used for generators.) However, for the state syntax, I would support using a colon or a double colon, so that it looked something like this:

<div if.bind="myPromise::resolved" />
<div if.bind="myPromise::rejected" />

The double-colon selector makes it clear that we are binding to something that isn't an object property. Further, it follows the CSS3 state selector syntax (think a:hover, etc), which is well-known. Finally, it's extensible - so one could feasibly do this:

<div if.bind="myRx::someRxStateWeMayNotKnowAboutYet" />

(This syntax could actually be useful for a whole lot of things. "router::requesting" comes to mind.)

I think this syntax might work well. it also wouldn't interfere with current parsing or binding behavior. (Unless the parser supports ES7 namespace-style bind syntax. I'm not a fan of that syntax, but what can you do, right. We might have to use a single colon to avoid that.)

What do you guys think?

Edit: I realize after the fact that the colon syntax was already proposed. Oops. It gets my vote, though, regardless of the vaule syntax - we might see something like ${(await foo)::rejected}.

jdanyow commented 8 years ago

I like the double colon too, unfortunately ES7's function-bind feature is using that operator.

I don't think binding behaviors are going to help us here- it's too ambiguous- it would still require us to try and figure out where the Promise/Observable is in the expression and wouldn't handle multiple observables/promises in the same expression well.

Maybe an operator is not a good fit? Should we have some function names the binding system recognizes? eg: ${async(myPromise.or.observable.expression) + async(foo).bar.baz} <div if.bind="settled(myPromiseOrObservable)">'

In the examples above, async and settled are "magic" functions recognized by the binding system. Of course this would mean developers could never bind to functions on their VM named async or settled. async is a keyword so that one shouldn't be a problem.

Thoughts?

niieani commented 8 years ago

I like using suggested by @jods4 binding behaviors for these purposes. It's extensible, as you can plug in your own custom behaviours without ripping out and modifying parts of the parser.

The problem begins when we have nested or chained access to a Promise - which one is it? Perhaps we could introduce sub-bindings or bindings or bindings to give Aurelia binding even more power? Then usage could be:

(obj.promiseProperty & async).property.anotherPromiseProperty & async: 'isResolved'

However, for Promises, I do also like the await promise syntax, since it's what's going to end up in native JS sooner or later anyway - it's good to support standards. This does present the question of how to handle "ready" / "resolved" value. Perhaps this could be solved if the async binding behaviour be the native way of handling a Promise-returning function, and the await keyword would simply be an alias to that binding behaviour?

@jedd-ahyoung Regarding your idea of double-colon: I think it's a very good alternative. And I don't believe it clashes with the ES7 this-binding syntax - it's ironic you say you're not a fan, since this is a great example of how it could be used! You'd just need to imagine isRejected or isResolved are functions that work on Promises or Observables as their this context. In that case aurelia-binding would need to implement the this-binding operator. Then isRejected, isResolved, and even async could be magic functions.

The usage could look like:

(await obj.promiseProperty).property.anotherPromiseProperty::isResolved()

I think the above is the cleanest solution and 100% conforming to the ES7 propositions (in the form they are now).

For Observables and other use cases, I think there are two solutions:

1. If the await in above example is a syntactic sugar for the above suggested "nested-bindings", like this:

(obj.promiseProperty & async).property.anotherPromiseProperty & async: 'isResolved'

Then the solution is to simply implement another binding behavior for Observables.

2. If the await in above example is a syntactic sugar for the this::bind operator, like this:

obj.promiseProperty::getPromiseValue().property.anotherPromiseProperty::isResolved()

Then there would have to be a decorator (e.g. @asyncBinding) for the functions that could be used asynchronously in the bindings, e.g.:

@asyncBinding
getPromiseValue(resolveBinding) {
  this.then((result) => resolveBinding(result));
  return "placeholder value";
  // the return value could be used as the result of the binding until resolveBinding(value) is executed //
}

To tell the truth I like the last version (with the ES7 bind operator) more than the nested binding behaviours, although both could be equally powerful.

Having such a decorator (@asyncBinding) would make it possible to implement methods also like in @jdanyow's last proposition. You could bind to the result of any async function, if the said function was decorated with @asyncBinding.

Finally I'd just like to say that supporting Observables are as important for me as Promises, especially that Observables are also on their way to being standardized in ES7.

jods4 commented 8 years ago

I'd also like to put to rest talk about "just do the right thing". We are not going to add logic to search for Promise and Observable instances throughout binding expressions. There are several reasons for this:

  1. Performance.
  2. Aesthetics: explicit is better. Think about the await keyword- it doesn't "find the promise" in an expression, it's passed an expression that evaluates to a promise.
  3. Level of effort to implement is too high. Would need to touch almost all the AST nodes.

EDIT Maybe it was not obvious but I assumed this was a comment against the tax.total & async binding behaviour syntax. If it was against a global "all bindings handle async magically", I totally agree with it.

Let's talk this through. If it's not feasable or has too much drawbacks, then fine. But I don't want to dismiss it too quickly because it's currently the proposal that I like best.

(1) Performance I agree it has too much impact to be always on. Now, the binding behaviour is opt-in so this becomes pay to play. The only consideration that remains is whether a long expression such as a.b.c[2].d + x.y() would be prohibitively expensive to compute when in fact it only contains one or two promises... We would need hard data to decide on this, but my feelings are that (a) quite often the expressions are going to be very short and simple. The expression above is just a bad practice anyway; (b) even so, the checks for promises aren't that expensive that a modern browser can't handle them. I am confident that as a pay to play the impact of "per binding expression" async support is very small.

(2) Aesthetics It's arguable. You will find that I am often a proponent of "explicit is better" and I don't like "magic" code but in this case I'm not sure that I'm with you. All other proposals look downright ugly and clutter. Some even want to introduce new syntax elements that could clash with future JS proposals (:: is proposed for function binding, .. could very well one day be a range or splice operator). So for "aesthetics" the global binding behaviour is rather good. For "expliciteness" you may have a point. I understand the behaviour as "this expression may contain async computations (promises), please handle them". That's an explicit opt-in but it doesn't say which parts are concerned... Is it important? Is there a case where you'd want to auto-handle a promise and bind directly to the next promise afterwards? I can't find a use case for this: (await promise1).promise2... Given that binding expression are (should be) short and contain few members, I'd argue the global opt-in is a fine enough granularity.

(3) Effort to implement Yeah this is the point that worries me most. I don't know what binding behaviour have access to, so I wondered if it would even be possible... If it's impossible / too hard it may be a deal breaker. I would love if it was attempted though. That syntax is nice and could be used for other libs (Rx comes to mind). If you have AST access, isn't a visitor enough? In the end, the number of nodes that need to be checked are few: variables, property access, indexer access, method call (do I miss something?).

I understand the concerns but I would like to validate them before dismissing this option. From everything I've seen in this thread so far, it's the one that I like most.

niieani commented 8 years ago

@jods4 the problem with "async per binding expression" is that you wouldn't be able to combine multiple async evaluations, e.g. how would you deal with this tenary use-case:

promise1::isResolved() ? promise2::getPromiseValue() : 'waiting'

It doesn't seem like a stretch to imagine someone might want to do this.

I'd still opt for implementation of:

100% ES7, no custom syntax added. The more custom syntax or magical behavior is added, the steeper the learning curve for Aurelia, and from what I understand, this is contrary to @EisenbergEffect's vision, who's is explicitly criticizing Angular2 for exactly that.

jods4 commented 8 years ago

@niieani It seems very strange to me that you display promise2 based on promise1 resolution?! This seems unlikely and could be accomplished by using if.bind="promise1 & async-resolved"; or a value converter that takes a promise as a parameter: promise2 & async | wait-for:promise1:"waiting"; or handled in the viewmodel directly. But again, rather far fetched in my opinion.

Now, if you had used the same promise twice, this seems a likely use-case. What I would suggest is adding a "fallback when async is not completed" parameter directly to the async behaviour. That seems reasonable to have a binding value until it completes. Something like promise & async:"waiting".

bind operator: I repeat, it does not clash, this is exactly one of the use cases

The spec is not final yet. With the current specs you are right it is valid ES code if we assume isResolved is a function in scope (I guess we could put them in global binding scope the same way we put other resources such as value converters).

Trick question: do we want to wait for the ES spec to finalize? What if we don't and it changes or gets dropped?

More importantly it needs special evaluation, unlike standard JS, which you tackle in the next bullet point.

a decorator for methods that should be evaluated asynchronously when invoked inside of a binding

In a dynamic language like JS, this seems a very fragile approach. Hard to say exactly what is what in an expression. Also, this means Aurelia needs to examine closely all binding expressions to check if a method call is one of those "known functions". This has a global perf impact, which needs to be assessed. From discussions above, pay to play seemed the desired approach to async bindings.

the alias await Promise keyword to tie it all together

Why not as it's part of ES7 syntax. It's not very flexible though: how do you define the expression value before the promise resolves? Only sane answer seems undefined (use a value converter to change it?). This is a shortcut that can only be used for promises values. It won't apply to promise state or Rx, etc. Not sure if it's worth the additional syntax in binding parser.

100% ES7, no custom syntax added.

This seems important to me. Anything else is: fragile (if future ES uses it); non-extensible (libs can't add new syntax, e.g. to support Rx); non-intuitive (for new users, as it's not a valid, well-known JS expression).

niieani commented 8 years ago

@jods4 I have to disagree with a couple of points:

It's not very flexible though: how do you define the expression value before the promise resolves? Only sane answer seems undefined (use a value converter to change it?).

The solution is in the example above (updated to add binding disposition):

@asyncBinding
getPromiseValue(updateBinding, disposeBinding) {
  this.then((result) => { updateBinding(result); disposeBinding(); });
  return "placeholder value";
}

The method's return value could be used as the result of that part of the binding until updateBinding(value) is executed.

If the result is undefined you could always write a binding like:

promise::getPromiseValue() || 'something'

No need for ValueConverters.

fragile (if future ES uses it);

Function bind operator is already strawman stage 2, await-async is stage 3. Both very likely to get implemented. If small syntactic changes are made to them, Aurelia's behavior could still be adapted to match once the final is settled. This is already the case with decorators - their syntax is changing and Aurelia will adapt once the final draft is ready. Even Observables themselves are stage 1, it's not only an "Rx library".

non-extensible (libs can't add new syntax, e.g. to support Rx); non-intuitive (for new users, as it's not a valid, well-known JS expression).

I'd say this is the most extensible syntax. What's not extensible here? This is all you need to implement Observables using this syntax:

ViewModel:

class SomeView {
  secondCounter = Rx.Observable.interval(1000);

  @asyncBinding
  observableSubscribe(updateBinding, disposeBinding) {
    this.subscribe((value) => updateBinding(value), (err) => {}, disposeBinding);
    return "placeholder value";
  }
}

View:

<template>
  ${ secondCounter::observableSubscribe() }
</template>

non-intuitive

That's hardly an argument. Any syntax proposted here would be non-intiutive, but I'd argue one matching the ES7 spec would be the most intuitive of them all.

niieani commented 8 years ago

I just realized the "binding methods" could have a syntax definition similar to Aurelia's binding behaviours or value converters. No need for decorators or the :: syntax:

export class SomeView {
  secondCounter = Rx.Observable.interval(1000);
}

export class MyCustomObserverBindingMethod {
  subscribe(context, updateBinding, disposeBinding) {
    this.subscription = context.subscribe((value) => updateBinding(value), (err) => {}, disposeBinding);
    return "placeholder value";
  }
  unsubscribe() {
    if (this.subscription)
      this.subscription.unsubscribe(); // or .dispose() in RxJS <= 4
  }
}

View:

<template>
  ${ myCustomObserver(secondCounter) }
</template>

Likewise, for Promises this could translate to simply using a binding like:

async(myPromise)
// and
asyncHasResolved(myPromise)
jods4 commented 8 years ago

@niieani

@asyncBinding
getPromiseValue(updateBinding, disposeBinding) {
  this.then((result) => { updateBinding(result); disposeBinding(); });
  return "placeholder value";
}

Not so fast :smile: I said it's not flexible and it is not, precisely because you hard-coded the value inside ::promiseValue() and the end-user can't easily change it. I suggested we should reasonably return undefined and returning a string is not any different.

OK you could pass the default value as a parameter like so p::promiseValue("placeholder"), which is after all what I said for the binding behaviour. Except...

It makes no sense to return a value in this method. Consider: p1::promiseValue1() * p2::promiseValue2() How do you combine your placeholders when there are multiple async methods in the expression and they don't agree on the placeholder value? A key difference here is that the behaviour would produce a default value for the whole expression, which just makes more sense.

For the rest you misunderstood me: I was agreeing on the fact that our solution should "100% ES7, no custom syntax added". We are in total agreement about this and barring the details discussed above and that :: is still in discussion at the standard body your proposal to use this syntax is OK with me.

niieani commented 8 years ago

I only used "placeholder value" as an example :palm_tree: . :D Obviously, the built-in function could have a customizable default value, as you suggested, like so:

@asyncBinding
promiseValueOrDefault(updateBinding, disposeBinding, default) {
  this.then((result) => { updateBinding(result); disposeBinding(); });
  return default;
}

The behaviour shouldn't produce a default value for the whole expression, as you wrote - that indeed doesn't any sense. It should only produce the value for that part of the expression, e.g.

// that's 5 * 7 in case of an unresolved Promise
(p1::promiseValue() || 5) * (p2::promiseValue() || 7)
// if using the above 'default' parameter, then it could be:
p1::promiseValueOrDefault(5) * p2:promiseValueOrDefault(7)

Happy to hear we're on the same page. :)

jods4 commented 8 years ago

I just realized the "binding methods" could have a syntax definition similar to Aurelia's binding behaviours or value converters. No need for decorators or the :: syntax.

Indeed :: is just syntactical sugar. I do like it, though. For binding the syntax is a lot nicer and "chains" better. Consider async(async(data).computation).total vs data::async().computation::async().total.

Because the expressions are equivalent, my comments above still apply.

(1) Analysing the expression to find magic methods is taxing perf and fragile. Only solution I see is passing the needed argument explicitely, but it's ugly and needs a new Aurliea magic word, maybe like so: data::async($binding)

(2) Placeholder I think placeholder for the whole makes sense, rather. Providing a substitute value for parts of the expression can easily be undoable.

Consider: a::value(0) * b::value(1), obviously numbers. What if your spec says you should display "n/a" until the values are available (because displaying any other value, even 0, is actually incorrect)? Is it good that after a resolves to 7 and later b resolves to 5 your display goes: 0 -> 7 -> 35?

Consider: data::value().firstName, an object, probably from a web service. Do you really want to write a default value for that? data::value({firstName: 'n/a'}) is awkward. Even worse when you consider: '${data::value().firstName} ${data::value().middleName} ${data::value().lastName}' (should be string template with a backtick)

I think this could be handled better with some kind of value converter: data::async() | if-undefined:"waiting".

niieani commented 8 years ago

(1) Analysing the expression to find magic methods is taxing perf and fragile.

How would it be different to what Aurelia currently does when you have binding behaviours or value converters? It would only have to be done when binding to a method (), just like it only has to be done when explicitly using a binding behaviour (&) or a converter (|).

(2) Placeholder ... Is it good that after a resolves to 7 and later b resolves to 5 your display goes: 0 -> 7 -> 35?

To mitigate that you would probably do this:

a::isResolved() && b::isResolved() ? a::value() * b::value() : 0

Display would go: 0 -> 35.

I don't think making such things built-in makes sense. Rather, this is a "good practices" thing that should go into the future docs.

Consider: data::value().firstName, an object, probably from a web service. Do you really want to write a default value for that?

I don't think you would ever have to worry about this. You should hide or disable such a whole block which could be done by a wrapper if.bind="data::isResolved()" or add a class that makes the field disabled. Or perhaps you could use a ref= that binds data::value() to resolvedData. Then you'd just write: resolvedData.firstName or resolvedData.lastName - if resolvedData is undefined, then Aurelia will silently evaluate those as empty.

jods4 commented 8 years ago

How would it be different to what Aurelia currently does when you have binding behaviours or value converters?

They are not in the middle of the expression. Aurelia finds them because syntactically they are separated from the expression by a | or a & delimiter (EDIT to be clear: what follows those delimiter is not an arbitrary JS expression anymore, it's Aurelia's own syntax). So it's cheap and not error-prone.

JS is not a statically typed language, so trying to figure out what a token actually binds to is a heuristic at best. And you would have to do that for each token.

To mitigate that you would probably do this:

I think you will agree this is ugly and not DRY at all. Logic to display a value until an async result is settled is something that should be simple and easy.

niieani commented 8 years ago

I was thinking about the best way "Binding Methods" could be defined. Here's an improvement over what I proposed before. Example:

export class MyCustomPromiseValueBindingMethod {
  constructor(defaultValue) {
    this.defaultValue = defaultValue;
  }

  subscribe(context, updateBinding, disposeBinding) {
    context.then((value) => updateBinding(value));
    return this.defaultValue;
  }

  unsubscribe() {
    // if anything needs disposing
  }
}

Now Aurelia would enable usage of these Binding Methods either with the :: function bind syntax or directly, i.e. bindings such as:

myCustomPromiseValue(property, 'something')

would be equivalent to:

property::myCustomPromiseValue('something')

So with regards to performance, the only check that would need to be done is: Is class MyCustomPromiseValueBindingMethod defined? If yes:

jods4 commented 8 years ago

I've been thinking about this some more. I thought about Rx rather than Promise just to get another point of view for a moment.

I ended up with two different ideas.

1. Magic function

Make it work with a plain function call: asyncValue(person).name or rxValue(quote).closing.

Now this is fine but can become not very fluent when sub-properties are in the chain, like: asyncValue(asyncValue(data).quote).closing.

If we adopt the proposed bind operator, this could be turned into "functional programing"-like syntax, which chains nicely: data::async().quote::async().closing. Same for Rx / Observables: quote::rx().closing. or any similar framework that doesn't immediately provide a value.

How could those magic functions actually work in the context of bindings?

My idea: they throw a well-known exception if they don't have a value yet. So when aurelia evaluates such a binding, the exception will short circuit the whole evaluation. Inside the exception is a well-known property, e.g. a signal, that the binding engine can subscribe to. At that point the binding value evaluates to undefined, or some other magic value. A converter could be used to turn that into another application-specific value if it requires so. The binding engine then waits for the signal. When it gets signaled the whole expression is evaluated again.

Using an exception here gives an easy way to pay-for-play (no check required when no async stuff is in the way) and an easy way to stop the whole expression evaluation without cascading ifs and returns.

2. Prototype enhancement

Something felt wrong in all that is above.

We are fighting hard to solve a problem that Aurelia already has an extensibility point for: observing changes.

The binding engine is extensible to observe all kind of objects. It supports observing array changes, map changes, property changes by instrumentation, by polling... It's easy to support observing changes to Promises or Rx Observables.

Consider what would happen if I extend Promise.prototype with a property, say async. It returns the promise result if settled or undefined otherwise.

Now I can write my binding expression like so: personPromise.async.name.

And the beauty of it, is that Aurelia will automatically use the ObserverLocator to find how to observe changes in personPromise.async. If we add a function getObserver() to our async getter... bingo :smile:

Exactly the same could be done for Rx Observables. We can extend the prototype with a rx property, whose getter has a getObserver and then this: quoteObservable.rx.closing would just work!

Isn't that nice? And the best part: it requires no change in Aurelia code.

3. Avoid prototype pollution?

One thing bothers me, it's the prototype pollution. Could we avoid it? Meh.. We could, but it requires modifying the BindingEngine in a dirty way (in my opinion). The problem is, in an expression such as quote::rx().closing (assuming that syntax was supported), Aurelia is not passing quote, rx to the ObserverLocator, which kind of make sense, because rx is only supposed to be a global function.

jods4 commented 8 years ago

For those interested, the following plunker is a proof of concept of option 2 above (works in Chrome). http://plnkr.co/edit/X21Zf48pEZGoLDcHORdo?p=preview

Notes:

Sample binding usage, where p is a promise that resolves to an object with property x: <div>${ p[$async].x }</div>

Exactly the same code could be used for other things, e.g. $asyncState, $asyncError, $rx, etc.

niieani commented 8 years ago

@jods4 That's a really cool hack - good thinking! Played around a bit, looks like a good pattern to use for the time being, before we get something out of the discussion here. However, it still felt a bit too much like a "hack".

I like the magic function concept a bit more, especially with the syntactic sugar of ::, but only as long as it's possible to manually define my own, custom "magic functions" (for example similarly to binding behaviours, as I proposed above). Promises and RX (or Most.js) are here today, but we have no idea what will be here tomorrow. The solution needs to be flexible and extensible.

jods4 commented 8 years ago

@niieani I agree asynchronous bindings have to be definable by users. As you said there are many frameworks.

Even something as simple as Promise could have multiple variations. For instance, if you work with Bluebird promises, then maybe you want to make a few optimizations as you can grab at the promise state synchronously, whereas the standard ES Promise only gives you .then() to work with (which is what I did in my demo).

Using Symbol feels actually ok to me. A non-enumerable, private symbol property is mostly undetectable by unaware code (not totally, but reasonably so).

The main problem with Symbol is: we kind of need to add a global scope to Aurelia. I think there was an issue about that somewhere and that it was resolved as 'bad practice, use the ViewModel dammit'. Which I feel a little bit bad about right now... Maybe we should reconsider? Another example I would like to see globally available is enums, so that stuff like if.bind='state === State.active' would work with State a global enum. Right now this can be worked around with defining

@computedFrom('state')
get isActive() { return this.state === State.active; }

Which is ok-ish, but adding a $async symbol on every VM feels a bit wrong. Especially as Aurelia would watch it for changes, which is pointless :(

Better support for Symbols! :fist: A middle ground could be an API to register symbols to Aurelia binding engine. That would alleviate the global scope issue and still provide symbols support.

Magic functions can work. Following points need to be addressed in my opinion:

niieani commented 8 years ago

I can just address your point on 'magic functions' and global scope (I'd prefer to call them binding functions, or binding methods) - the scope would not need to be global, it should be the same as with binding behaviours - you use <require> to load the appropriate behaviour. I think they can be convenient even without the :: operator. After all, it's not that often you need to do complex bindings in the Views - if you need to, you should be doing that in the ViewModels anyway. And for those times when you need to, having :: would be nice, but definitely not a requirement.

It would be a great time now to have @jdanyow chip in a few cents on the matter and his point of view.

jwahyoung commented 8 years ago

I am a fan of magic functions. If we use them, however, they should be prefixed. Aurelia uses the $ prefix for certain "magic" properties, like $parent and $index. Might that apply here, with the use of something like $await() or $async()?

I do like the binding syntax in this case - if only because I can't think of anything better that would actually work well with all of the requirements that we have.

I'm not a huge fan of having to use the decorator syntax within a viewModel to have a function call work on a bound property, though.

I don't think that the "magic" functions (basically, what Aurelia would include by default) would need to be required. However, having a require tag for user-defined functions could work well, as we could define them as global resources if necessary.

I think that the binding syntax could be useful, as functions could be used with or without the syntax depending on user tastes.

I'm not sure, but this might beg the question to a certain extent....if we're talking about ES6 syntax and binding expressions, wouldn't it be somewhat logical just to do something like this:

value.bind="myObject.myPromise::then(result).value"

Just throwing it out there. I know there are a lot of issues with that specific line, but I'm hoping that it might trigger an idea.

2016-02-16 19:45 GMT-05:00 Bazyli Brzóska notifications@github.com:

I can just address your point on 'magic functions' and global scope (I'd prefer to call them binding functions, or binding methods) - the scope would not need to be global, it should be the same as with binding behaviours - you use to load the appropriate behaviour. I think they can be convenient even without the :: operator. After all, it's not that often you need to do complex bindings in the Views - if you need to, you should be doing that in the ViewModels anyway. And for those times when you need to, having :: would be nice, but definitely not a requirement.

It would be a great time now to have @jdanyow https://github.com/jdanyow chip in a few cents on the matter and his point of view.

— Reply to this email directly or view it on GitHub https://github.com/jdanyow/aurelia-async/issues/3#issuecomment-184944106 .

niieani commented 8 years ago

I've implemented my idea of BindingFunctions. You can find the working plugin here: aurelia-binding-functions.

The conventions to create a BindingFunction are the same as with ValueConverters or BindingBehaviors, i.e. you name your BindingFunction class with the suffix BindingFunction, e.g. AsyncBindingFunction and then you can use @async(params) in your bindings.

Along with it, I've created two plugin packages that implement:

@EisenbergEffect @jdanyow It would be good if there were an official way to register custom view resources and their lookupFunctions. I've had to monkey patch quite a few things (ModuleAnalyzer, ViewResources).

I'm also currently working on an aurelia-cycle plugin that will make it possible to use Cycle.js inside of Aurelia.

EDIT: I've added the @ prefix so that BindingFunctions won't collide with methods in the binding context.

EisenbergEffect commented 8 years ago

You can absolutely register custom view resources. I think the trick in this case is that it's not easy to register a convention associated with the view resource...which leads to the big hack on the module analyzer. We can look at extending that in some way.

@jdanyow Would be better to comment on how the rest was extended since that has more to do with the binding system.

niieani commented 8 years ago

Of course, that's what I meant @EisenbergEffect (intirely custom view resources, not just user-made), though it's not only custom conventions, but also custom lookup functions, which are currently hardcoded. In fact, the ModuleAnalyzer hack is not that big (only these three lines, rest is copied from Aurelia master), it's just that I found no way to reapply the original method while hooking my own convention at the same time. I would appreciate any comments on the code quality, I've tried to make the implementation as simple and minimal as possible.

jdanyow commented 8 years ago

@niieani if you're moving forward with this approach I think the most performant way to go about it would be to change these lines in the parser (via monkey patching the parseAccessOrCallScope method:

https://github.com/aurelia/binding/blob/1.0.0-beta.1.3.0/src/parser.js#L334-L338

    if (this.optional('(')) {
      let args = this.parseExpressionList(')');
      this.expect(')');
-     return new CallScope(name, args, ancestor);
+     let ctor = this.scopeFunctions[name] || CallScope;
+     return new ctor(name, args, ancestor);
     }

This change assumes you've added a property to the parser: scopeFunctions = {}; and the corresponding API:

ParserImplementation.prototype.scopeFunctions = {};
ParserImplementation.prototype.registerScopeFunction = function(name: string, expressionClass: new(name, args, ancestor) => Expression): void {
  this.scopeFunctions[name] = expressionClass;
}

Usage:

import {ParserImplementation} from 'aurelia-binding';

export function configure(aurelia) {
  let parser = aurelia.container.get(ParserImplementation);
  parser.registerScopeFunction('@rx', RxObservableExpression);
  parser.registerScopeFunction('@settled', PromiseExpression);
}

What do you think? I haven't tried this out to make sure it fully works, but it's what I had in mind if people liked the magic functions idea.

niieani commented 8 years ago

@jdanyow That does look pretty good, however going this route I loose the possibility of locally-scoped user-defined BindingFunctions (ValueConverter-like behavior) which I quite liked, plus each Expression implementation is more complex. I did implement your suggestions though, while retaining BindingFunctions functionality and it works wonderfully. Now there is either the direct, low-level route of defining a ScopeFunction, or the easier, more dynamic way of defining a BindingFunction.

I still needed to create bind and unbind methods on AccessScope and AccessKeyed though, otherwise the first parts of bindings such as: @rx(mouse).clientX didn't get bind() / unbind().

jods4 commented 8 years ago

@jdanyow @niieani If this might go into Aurelia core (not just monkey-patching) there needs to be more design / thinking. This would be a new primitive/capability of binding expressions so we should make it as generic and as useful as possible -- and make sure it's worth its cost.

niieani commented 8 years ago

@jods4 what do you have in mind in terms of more design? I agree that flexibility is most important when it comes to pluggable features, as is with ValueConverters/BindingBehaviors and now my BindingFunctions / @jdanyow's ScopeFunctions. During development however, I wished the Expression interfaces were simpler (I think they're overly complex now).

Personally, I think a push interface would make more sense than the current trigger & pull interface, i.e. I wish binding expressions were more like Observables. As I understand, what we have now is that the binding is being informed that it should be updated, and then Aurelia proceeds to evaluate() its the current value, and asks it how it can inform itself of new updates (connect()).

Another issue I had with Expressions is that the evaluate() method is used for two separate things - getting the current value of the binding - and for triggering an action. So inside the evaluate() method of Expression it's impossible to distinguish whether the evaluation is related to a click.trigger or a value.bind. To decipher this for Rx.Subjects I have to make a "hacky" distinction, by checking whether the bindingContext contains $event. Wouldn't it make more sense for click.trigger (= Listener scope) to call assign instead of evaluate? Clicking is more like "assigning" a new, ephemeral value, in that it is a user-triggered action, just as typing into an input is.

To go with my Rx Subject analogy, every time the user changes the value of a two-way binding, we'd call subject.next(newValue), while every time the user clicks on something we would call subject.next(event). We're feeding new data into the Observable, as opposed to retrieving (evaluating) it.

To simplify, I like to think about the direction of data-flow in these 3 cases:

example data-flow visualization
user typing inside an <input> that can clear on submit two-way View => ViewModel => View
displaying a variable in <p> one-way1 1exclusively from ViewModel => View
user clicking a <button> one-way2 2exclusively from View => ViewModel
jods4 commented 8 years ago

@niieani

ValueConverters, BindingBehaviors and now my BindingFunctions During development however, I wished the Expression interfaces were simpler (I think they're overly complex now).

Building a general-purpose framework is a fine art. Each new concept introduces additional complexity, both for the newcomers (users), long-term maintainability and performance (if only size). That's why we have to think every addition carefully.

A first question that needs to be answered is: do we really need a new concept or not? Not saying this was the right thing to do but I showed above that we could achieve this result with a nice syntax and no new concept at all.

what do you have in mind in terms of more design?

Let's say we agreed that a new concept is required here, maybe BindingFunction.

Global scope? Obviously we need to put these in the binding scope somehow... @jdanyow uses registerScopeFunction. This is a specific solution, wouldn't it be interesting to have a generic solution instead? I pointed out above that using Symbol in bindings is not convenient because you have to add your symbols into your VM. The same thing can be said for enums, or helper functions such as your BindingFunction. Wouldn't it be nice if we solved all those problems with a single, global scope management? There was an issue about that, which was discarded but I wonder if we should revisit.

Also, the solution should think a little bit about possible tooling. There is some thinking going on to support Typescript in templates (e.g. Aurelia, Angular, React, etc). It would be nice if tooling would have a way to know what is in scope.

Observability? The culprit here is that BindingFunction/ScopeFunction has special observation mechanics, right? Because right now only properties and collections are observable?

Special cases are never nice designs... Wouldn't it be better if we extended the binding engine design so that any function could be observed, like properties? Maybe by opting into that with a decorator? That could be useful in more situations, including functions on my VM.

And if we do that the BindingFunction concept is no more. They are just regular observable functions in a global scope.

Naming We need to come up with a clear, simple, two-liner explanation of what the concept is; and give it a clear name because BindingFunction or ScopeFunction are a little vague in my opinion.

Not saying that I have the right answers, but we have to think about those questions before adding new concepts for one specific usage.

niieani commented 8 years ago

@jods4 Regarding "do we need a new concept" - the answer is no. We already have that concept and it is a binding Expression. The only problem was that it is not exposed to Aurelia users, as it was only an internal concept used to create bindings. Now, in my opinion we could still think about how Expressions themselves could be simplified before we give users the possibility to create their own, natively in Aurelia - I wrote my comments regarding that in the previous post.

Regarding your comments on scope, in my implementation of BindingFunctions the scope is not global. As I noted before, it works the same as ValueConverters - you can <require from="./myBindingFunction"> or just place a MyGreatBindingFunction in one file together with your ViewModel and that BindingFunction will only work in that View, referenced by @myGreat(). On the other hand, @jdanyow's ScopeFunction is always in the global scope.

Regarding Observability - I've recently stumbled upon this project: https://github.com/mobxjs/mobx and I really the concepts it presents. Perhaps an inspiration?

Regarding Naming: I'm not sure what could be clearer - a BindingFunction it a function that you can bind to, just like you can bind to a property. Think of the analogy with properties: just as properties can both: be changed by the View and trigger the update of the binding, Binding Function can trigger changes that, likewise - update the binding with a new value, or can themselves be asked by the View to update its value.

The key thing to understand @jods4 here is that neither my plugin, nor @jdanyow's ScopeFunction idea created anything that wasn't already there: there's no added complexity, I only exposed some of the existing complexity to the end-user. If they choose to use it, all power to them, if not, that's great too!

jods4 commented 8 years ago

@niieani There are definitively new concepts inside Aurelia here. You have patched the lexer and parser to introduce the BindingFunctionScope and BindingFunction support. So either BindingFunction is a new built-in Aurelia concept, or if it stays in a library you need new extensibility concepts in Aurelia.

Let's put it that way: what changes in Aurelia do we need to do to enable that scenario without monkey-patching?.

Regarding scope: I don't think it's exactly the same as ValueConverters. Because converters or behaviours are not used inside a JS expression. They are special objects that appear in a dedicated position outside the JS expression, inside the binding syntax. Scoping-wise, ValueConverter are closer to a CustomElement or CustomAttribute for that matter.

Your version doesn't use a global scope but it introduces a local scope just for BindingFunctions. If we introduce a way to expand the JS scope inside bindings, wouldn't it be better to do it in a way that's usable for other purposes?

Regarding naming. We can already bind to a function... I guess what makes yours special is that it's observable. Maybe we should call them ObservableFunction then? Which begs the question: why not introduce support for turning any function observable?

Regarding JS syntax. I saw that you introduced a new escape character @ to indicate binding functions in an expression. I don't feel good about that. This makes the expression not compliant with JS standard. If @ is ever used by ES for another purpose it's going to become a problem. Also Rob seems to care a lot about staying standard-compliant.

EisenbergEffect commented 8 years ago

Perhaps on a related note, the latest release of templating allows view resources to tap into a beforeBind hook. The hook can be used to add methods or properties onto the overrideContext for a view, making them available for use in expressions.

jods4 commented 8 years ago

@EisenbergEffect wooo that's very shiny! I think we can do great things with that. Including the stuff discussed here, but not only! Any doc or sample code online?

niieani commented 8 years ago

@jods4 Initially (in v0.1.0) I didn't use the @ character, but that meant that the BindingFunction name could be colliding with a property/method of the same name on the ViewModel. The @ character is already used by the future ES7 for the purpose of decorators, so I "took it" for my BindingFunctions. I'm not saying this is "the way", but I don't see using decorators inside of bindings, and it even makes sense - you can think of @async(promise) as a decorator for the actual value or promise. Plus, I'd argue that Binding Expression strings are already not compliant with JS, because they contain the | or & as you have pointed out yourself.

Besides, I wasn't creating something that's supposed to be integrated into Aurelia, this is a plugin and you can either use it or not. I expect a native implementation to be exactly, as you have put it, the answer to this question:

why not introduce support for turning any function observable?

This is essentially the problem we are trying to solve from the start: a way to turn any function (?) into a bi-directional "observable" would be ideal, and this is what my plugin currently tries to do. As for the name, ObservableFunction is quite good, but does not convey the purpose of such a "function" in it's entirety, because Observables can send multiple values over-time, but are not assignable. In the Rx terminology, an Observable that is also an Observer is called a Subject.

Observable = in other words a stream of values = you can subscribe to it and the values will be sent out over time Observer = you can push new values to it Subject = Observable + Observer

Functions do not, and cannot meet that criteria, because you can only call them and retrieve their result - period. I'll also address the solution you posted for resolving promises - it works fine for simple things like Promises, but it is only useful for one-way bindings, and involves large amount of boilerplate and prototype modification, and in the end you still were faced with the need to register a "global" name with the value of the symbol. We need something better.

@EisenbergEffect I saw the hook was added, and that's great news indeed, but I'm saddened that there's a beforeBind but not an afterBind hook, which would also be useful, e.g. in my implementation of Cycle.js in Aurelia (need to monkey patch otherwise).

EisenbergEffect commented 8 years ago

More hooks can be added in the future, but we can't ever take any away, so we try to be conservative with that. We don't want to cause a performance problem with too many hooks. I'd like to hear if you've tried the new hook and why it won't work for you.

jods4 commented 7 years ago

A new method to implement this occured to me, so I thought that I would share. Instead of trying to describe the promise/rx nature of a property at usage site (i.e. in the binding, via a magic function), we can do that at declaration site (in the viewmodel).

The following is doable:

class ViewModel {
  @rx user;
}

And then the following binding would update properly, when the user observable pushes a new value:

<div>${user.name}<div>

To me, this looks by far the cleanest solution so far.

niieani commented 7 years ago

@jods4 this is what I've experimentally implemented here. See https://github.com/aurelia/binding/pull/440 for more info. I have managed to find a way to do this opt-in too (have it running locally), so the mentioned PR into aurelia-binding isn't necessary. I'll publish it once I have a bit more time to polish it and write some docs.

jods4 commented 7 years ago

@niieani code looks complicated! I have no implemented this myself, so maybe I'm wrong, but I thought it would be simpler.

My idea is to use a decorator to:

  1. turn the field into a getter/setter (trivial).
  2. provide an observer for the property, either via getObserver() on the getter or via a observer adapter (trivial).
  3. unfortunately, this means the getter/setter must re-implement what the SetterObserver does (not complicated, but longer).
  4. add the Promise/RX/etc. completion on top of that (not complicated).
niieani commented 7 years ago

It's very easy actually, perhaps you're looking at the implementation code, not the user-facing code? It's an abstraction with which it would be real easy to implement your cited code:

class ViewModel {
  @rx user;
}
jods4 commented 7 years ago

@niieani yes, I was looking at the implementation ;)

TheNavigateur commented 7 years ago

await is the correct ES-based syntax for the result of a resolved promise as an expression.

e.g. in TypeScript/Javascript it's (already supported in Chrome/Firefox):

const name : string = (await this.getResultPromise()).name;

So in an HTML template expression it makes perfect sense to have the same:

<div>${(await getResultPromise()).name}</div>

For the result of a promise rejection, in TypeScript/Javascript it's:

try{
    await this.getResultPromise();
}
catch(e){ //e is whatever was passed in to reject(e)
    //e.g.:
    //e.message
}

This doesn't straightforwardly map to a linear template expression using the promise, so for this and only this I would introduce a catch keyword as:

<div>${(catch getResultPromise()).message}</div>