-
Notifications
You must be signed in to change notification settings - Fork 105
async getters discussed? #15
Comments
I guess the reason this is a tough call is highlighted by your post. Namely, it doesn't seem appropriate to cache the computation---getter function code is currently executed every time the property is gotten, and async getters should not break this expectation. But then you're doing a potentially-expensive async transform every time. One use case I could imagine is adding a class Thinger {
constructor() {
this._value = getValue(); // returns a promise for an X
},
async get loaded {
await this._value;
// don't return anything; loaded getters should not have a value
}
} But this is probably just better written as class Thinger {
constructor() {
this._value = getValue(); // returns a promise for an X
},
get loaded {
return this._value.then(() => {});
}
} |
@domenic So I can follow, could elaborate on why caching a getter's result internally is a bad practice? IMO, since getters are abstractions so consumers don't need to know the property is even computed, I would think caching the result could even be a best practice if the computation is intensive and a property is preferred over a more revealing method like |
Caching is fine, if it's performed by the getter. But the code that appears inside |
@domenic Hmmm. I understand, but I don't know of any case where it would be possible to change that behavior? |
Well, maybe I made a wrong assumption, but I assumed that you were proposing that your "Not currently supported by traceur or spec" example desugar to your "But you can work around this spec omission by doing this" example. |
@domenic That was a correct assumption, effectively. Thinking about the translation in my head, the promise returned might be different in the former, but would resolve effectively the same. But just to clarify, the caching part is purely a usage example, not that the language itself should cache anything behind the scenes--definitely not. Maybe this will help clear things up. Here's another example of the proposed, where the developer decided not to ever cache anything: class Article extends Post {
get async comments() {
let comments = await fakeAjax();
comments[0].body += ' of the day';
return comments;
}
}
async function main() {
let article = new Article(123, 'Hello World');
// Wait for comments to come back from the server
let comments = await article.comments;
console.assert(comments[0].body === 'First comment of the day');
} |
As a real-world example, nested models in Ember Data are now always resolved asynchronously. With the var article = this.store.find('article', 123);
article.then(function (article) {
article.get('comments').then(function (comments) {
console.assert(comments.objectAt(0).get('body') === 'First comment of the day');
});
}); vs. syntax in 2023 let article = await this.store.find('article', 123);
let comments = await article.comments;
console.assert(comments[0].body === 'First comment of the day'); |
Good topic. There is no technical reason for this to be disallowed - it is indeed an artificial restriction in the current proposal. If it was supported though, I don't think it would mean exactly what the proposed rewrite/workaround above suggests. In your code example, the That would still allow the kind of code you show in your Ember Data example, but would require any data-binding framework that saw this kind of object to be able to deal with data-binding promise-valued prototype properties. The rewrite/workaround that would give you the same behaviour would be: class Article extends Post {
constructor(id, body) {
super(id, body);
this._comments = null;
}
async _fetchComments() {
if(this._comments === null) {
this._comments = await fakeAjax();
this._comments[0].body += ' of the day';
}
return this._comments;
}
get comments() {
return this._fetchComments();
}
}
async function main() {
let article = new Article(123, 'Hello World');
// Comments may or may not be loaded yet
let comments = await article.comments;
console.assert(comments[0].body === 'First comment of the day');
} The questions then are:
I know C# has this same restriction for their |
My point about |
@lukehoban You're absolutely right about my workaround vs. yours. I was trying to accomplish that, but mistakenly believed that
@domenic's case as well as existing paradigms in the wild, like Ember Data, suggest that this point may be a interesting question to debate but fruitless in the end, much the same as the never-ending debate about whether getters are bad, etc. i.e. there is a clear usage for them as they are being used today.
I think if you can All of this is IMO, of course. 😄 |
Here's what I can dig up so far about C#:
http://blogs.msdn.com/b/pfxteam/archive/2012/04/12/async-await-faq.aspx (sorry they don't have element id's I can directly link to) This is (probably) not the language designers' reasons, however. |
Looks like @stephentoub wrote that article, maybe we can yank him in here for insight? |
Actually was just chatting with @stephentoub and Lucian Wischik about this. They pointed out a key problem. The desugaring I shared actually has a bug - it will allow the ajax request to be kicked off multiple times, and will only start caching once one of the requests completes. This is almost certainly not the intended behaviour. The intended behaviour would more likely have been: class Article extends Post {
constructor(id, body) {
super(id, body);
this._commentsP = null;
}
async _fetchComments() {
var comments = await fakeAjax();
comments[0].body += ' of the day';
return comments;
}
get comments() {
if(this._commentsP === null) {
this._commentsP = this._fetchComments();
}
return this._commentsP;
}
}
async function main() {
let article = new Article(123, 'Hello World');
// Comments may or may not be loaded yet
let comments = await article.comments;
console.assert(comments[0].body === 'First comment of the day');
} However, you can't desugar an async getter into this, because you would be "inside" the promise, and couldn't reach out to cache the promise value you were computing. So, in practice, it wouldn't be possible to write a correctly caching getter without basically writing it in the workaround style. I think this is basically the ultimate reason the feature doesn't make sense. |
@lukehoban Ohhhh... EDIT: DUH JAY.....ignore everything below....it's pointless cause it doesn't use
|
Let's try that again...This appears to pass my tests. Replacing class Article extends Post {
constructor(id, body) {
super(id, body);
this._comments = null;
}
get async comments() {
if (this._comments === null) {
this._comments = fakeAjax();
let comments = await this._comments;
comments[0].body += ' of the day';
}
return this._comments;
}
} By all means, find the flaw. I think this could also be written as: get async comments() {
if (this._comments === null) {
await (this._comments = fakeAjax());
this._comments[0].body += ' of the day';
}
return this._comments;
} |
Agree with Luke - let's keep things simple and rock-solid. Also, at a more conceptual level, async functions always express operations. Accessors, on the other hand, express properties. Those properties are virtual, of course, and accessing them might trigger off asynchronous operations, but to the consumer it must appear as a property of the object. |
Good topic. I agree with Kevin, getters are supposed to represent properties which represents state. Therefore these are better done without async even if the the getter returns a Promise. Lets leave async getters out of this proposal. |
👍 |
I'm not sure if this restriction will continue to make sense. Decorators in particular make it possible to move concerns such as caching out of the getter code, so I think its worthwhile to revisit this for the future? |
Curious if there's been any discussion about async getters?
I can see arguments both ways, but the spec currently allows you to
await
regular properties, so why not properties that are computed via a getter?Just looking to start an open dialog if one hasn't already.
Some examples for reference...
Assuming this setup:
Currently supported by traceur:
Not currently supported by traceur or spec:
But you can work around this spec omission by doing this:
The text was updated successfully, but these errors were encountered: