New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add runAsync() directive #657
Conversation
f5858f7
to
d8bbf9e
Compare
| new CustomEvent('pending-state', { | ||
| composed: true, | ||
| bubbles: true, | ||
| detail: {promise: pendingPromise} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider trying to reuse the same pendingPromise when a new promise supersedes a previous unresolved one, and avoid firing the event multiple times?
|
|
||
| const runs = new WeakMap<Part, AsyncRunState>(); | ||
|
|
||
| export type Task<K> = (key: K, options: {signal?: AbortSignal}) => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed, key (singular) should be changed to dependencies (plural, array)
| if (currentRunState !== runState) { | ||
| return; | ||
| } | ||
| part.setValue(success(value)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensure the template result for the current success/pending/initial/failure state is re-evaluated (with e.g. the last resolved/error'ed value) for each call to the directive, not just on dependency invalidation, to avoid a footgun where other props in the closure are used in the TR but do not update on change.
We should also be clear in the docs that the dependencies (née key) are what trigger the async function running and thus generating a new promise; however they do not "guard" the template result functions in the way that guard directive would. If the TR functions are expensive, then they would need to nest a guard directive in that value (unless we remove that ability -- in which case there is a slight capability hole there).
| }); | ||
| assert.equal(stripExpressionMarkers(container.innerHTML), 'Pending'); | ||
| await 0; | ||
| assert.equal(stripExpressionMarkers(container.innerHTML), 'Initial'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make runAsync wait a microtask after running the async fn to get a shot at rendering initial before pending?
d8bbf9e
to
86e4768
Compare
| const hasAbortController = typeof AbortController === 'function'; | ||
| const testIfHasAbortController = hasAbortController ? test : test.skip; | ||
|
|
||
| suite('runAsync', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add tests:
- Multiple dependencies
- Add use of props from render closure in template result functions, and ensure they re-evaluate correctly each render
- Ensure
pending-stateevent is not fired in initial state
c487b1d
to
8ce8cc6
Compare
|
So there's good news and bad news. 👍 The good news is that everyone that needs to sign a CLA (the pull request submitter and all commit authors) have done so. Everything is all good there. 😕 The bad news is that it appears that one or more commits were authored or co-authored by someone other than the pull request submitter. We need to confirm that all authors are ok with their commits being contributed to this project. Please have them confirm that here in the pull request. Note to project maintainer: This is a terminal state, meaning the |
| rejectPending = rej; | ||
| }); | ||
| const promise = task(key, {signal: abortSignal}); | ||
| // The state is immediately 'pending', since the function has been |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this mean that even if the promise can't be run with the data available it will take always take two renders to get to initial? One pass for pending and then a second (assuming throw new InitialStateError()) to trigger initial. If so, that seems confusing to end users.
This may be conflating the responsibilities of lit-html and a component system (i.e. LitElement) that relies on it, but I would expect more mainstream usage of this to rely on a key that powered the promise in someway so an initial call where key === undefinedorkey === ''would provide theinitial` render template in a single render pass.
In this case, would you expect there to be additional wrapping of or an alternative to this directive to achieve that functionality? or, would it make sense to include that by default?
| } | ||
|
|
||
| // If the promise has not yet resolved, set/update the defaultContent | ||
| if ((currentRunState === undefined || currentRunState.state === 'pending') && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @justinfagnani : I am experimenting with this a bit since you mentioned it last week. I notice that the pending state is not applied on first key/dependency change after a previous success state - it remains in the success state. Another key change is required for the pending state to be applied.
I could be doing something incorrectly, but I think : on line#93 runs.set(part, runState); is called to set a new pending state, but currentRunState is still the previous success state, so pending() is not called here. Once the directive is run again (ex. second key-stroke in the search demo), currentRunState is initialized with the most recent pending state, and then pending() is called here. It is perhaps not super noticeable when there are many changes (like input event). The absent intermediate pending state was fairly obvious in my case where there aren't multiple key changes quickly in succession.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would agree with this. Won't we need to reacquire the values for the current run if we ever want to be able to return to the "pending" state on subsequent uses?
I've been playing with the code shaped this way at https://stackblitz.com/edit/lit-element-run-async-2?file=run-async.ts
| if ((currentRunState === undefined || currentRunState.state === 'pending') && | |
| const runState = runs.get(part); | |
| if ((runState === undefined || runState.state === 'pending') && |
| // Wait a microtask for the initial render of the Part to complete | ||
| await 0; | ||
| const currentRunState = runs.get(part); | ||
| if (currentRunState === runState && currentRunState.state === 'pending') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you only dispatch a promise when currentRunState.state === 'pending' an action that immediate throws new InitialStateError() won't be provided the pendingPromise reference to manage the reject that happens on line 108. Should this be updated to:
| if (currentRunState === runState && currentRunState.state === 'pending') { | |
| if (currentRunState === runState && (currentRunState.state === 'pending' || currentRunState.state === 'initial')) { |
|
Closing in favor of reactive controllers in lit-next. |
Adds a
runAsyncdirective.This directive takes a key object, task function, and a set of templates to render based on the state of the task (initial, pending, success and failure).
The API is slightly different than what I referenced in #649: This is a plain directive, and not a factory. A factory can be created by currying this directive, or a directive tied to a specific task function can be created via composition. The templates have also moved to a object with named properties.
Fixes #649