With Octane, we got some nice and usually helpful errors warning about updating properties that could lead to infinite revalidation bugs, e.g.:
Assertion Failed: You attempted to update `isPending` on `AsyncResource`,
but it had already been used previously in the same computation.
Attempting to update a value after using it in a computation can cause logical errors,
infinite revalidation bugs, and performance issues, and is not supported.
Now generally this is great, but I’ve found places where this is - to me - quite hard to follow and reason about, and I wonder if it might be a bit overzealous in some cases.
For example, I’ve been playing around with building a minimal async resource (until `@use` and Resources by pzuraq · Pull Request #567 · emberjs/rfcs · GitHub lands) implementation like this:
import { tracked } from '@glimmer/tracking';
import { restartableTask } from 'ember-concurrency-decorators';
export class AsyncResource {
promise;
@tracked isPending = false;
@tracked value;
get state() {
return {
isPending: this.isPending,
value: this.value
};
}
async updatePromise(promise) {
if (promise === this.promise) {
return;
}
this._runPromise.perform(promise);
}
@restartableTask
*_runPromise(promise) {
this.promise = promise;
this.isPending = true;
let value = yield promise;
this.value = value;
this.isPending = false;
}
}
With an accompanying component:
import Component from '@glimmer/component';
import { AsyncResource } from 'fabscale-app/utils/async-resource';
/*
* This component takes a `promise` argument and yields an object with a `value` and a `isPending` property.
* Note that the `value` will only change once the passed in promise is resolved!
* This means that the last value will be used until a new value is resolved - it will not f.e. switch to `null` in between or similar.
*/
export default class AwaitPromise extends Component {
/*
* Arguments:
* - promise
*/
_asyncResource;
constructor() {
super(...arguments);
this._asyncResource = new AsyncResource();
}
get promiseData() {
this._asyncResource.updatePromise(this.args.promise);
return this._asyncResource.state;
}
}
So usage is like this:
<AwaitPromise @promise={{this.myPromise}} |promiseData|>
{{log promiseData}}
</AwaitPromise>
Now to me, it looks like this should work, but it triggers the invalidation error. I don’t think (?) this is a bug per se, but basically wanted to ask if that is expected/desired behavior? Or if there are any other ideas on how to better realize such behavior? I ended up with a considerably more complex code where I keep track if the promise has ever resolved and a sprinkle of yield timeout(1)
before setting this.isPending
, which kind of works but feels rather over-complicated.