-
Notifications
You must be signed in to change notification settings - Fork 45
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
A near-synchronous priority higher than "user-blocking" #112
Comments
The last use case at the very end of your comment clarifies your motivations best for me: WICG/webcomponents#1055. As you say, there are many framework schedulers that do this, and, perhaps some primitives are needed to help build those. My gut reaction is that just a platform "task scheduler" is not really the right mechanism for accomplishing these goals-- but it certainly seems worthy of exploring. Do proposals like Signals or Observables also help address these use cases (perhaps more directly)? Those at least have similarities:
and
...But those can more directly model some of the problems of Batching and Ordering than just pure opaque task scheduling can. You specifically point out problems of:
...and this implies that parents would want to abort and/or adjust scheduled tasks as triggered effects change... This sounds like a higher order problem. A solution to that problem might need to leverage some missing task scheduling primitives. Some of your examples (Virtualization libraries, reading layout props after render complete) seem to me to risk being antipatterns (with lots of layout thrashing) if implemented incorrectly. You show an example of using I think these use cases should be considered, and are related to In your "examples" section:
I think the order of your example, even with the tree-aware-scheduling, would actually have been:
...In other words, the contents of the A() postTask should only start to execute after the current task yields. I think any other "implicit behaviour" such as automatically starting to run the outer-most postTask would be... surprising. However, we've heard requests for something like a If you register tasks with a custom TaskController, you allow the platform to schedule those as distinct "macrotasks" at some given priority. But with a theoretical (The original use-case there was for document unloading type use cases, and other |
I think there is a need for a priority that's higher than "user-blocking" for certain DOM rendering cases.
This priority would be synchronous wrt the outermost
scheduler.postTask()
call, but nested tasks would have the same ordering as they do now: nested tasks would run after their parent task's body, but before the parent'spostTask()
returns.Motivation
Often you need to update DOM and coordinate among multiple actors in parent/child relationships - one of the original use cases for the scheduling API. Sometimes the coupling between parents and children is very loose. The parent doesn't call an API on the child to get it to update it's DOM, but it may cause one or more state changes that the child reacts to by updating.
The requirements on this kind of loosely-couple system are:
Using either the current scheduling API, or just relying on microtasks, you can do a pretty good of getting parent -> child rendering order and batching. Each component adds it's update task to the microtask queue. In that update task they create and modify children. The children then schedule their own update tasks in response to those changes, which are added to the queue. Eventually the whole tree of components has added and executed their tasks in top-down tree order, and the update is complete.
This works pretty well, if you can live with the asynchronicity.
There are two main problems though:
Asynchronicity in general. Many use cases call for being able to update DOM and synchronously rely on the changes. These cases are often related to measurement, events, and shadow DOM slotting.
Some cases where async is problematic:
For example, with this HTML:
The
<parent-element>
may render a shadow root with a<slot>
and an event listener on that<slot>
(or a container of it). If the child first an event synchronously, the parent won't have rendered the slot and added the event listener, and will miss the event.Knowing when a tree of updates have completed. There is no way to know when the microtask queue has been fully flushed. Users of components often want to do something with the component, and even if they can run in an async context, have a hard time knowing when the component is finished updating.
Being able to have the outer task be run synchronously would mean that this construction is always safe:
and component users could do this:
Something that native elements can do.
Examples
Here's an example of two functions that produce tasks and the timing that would be ideal:
With user-blocking priority, this produces the log:
Ideally, we would produce this log:
Hazards
I presume some people will have the immediate reaction of thinking that a sync API is too hazardous - that it would encourage the read/write striping that can cause a lot of blocking layouts. I think this is somewhat true, but modern DOM rendering libraries have encouraged a structure of code and declarative templates that largely eliminate this problem. Yet those rendering libraries often have their own internal schedulers that can schedule updates exactly as described here: synchronous to the outermost layer, batched and tree-ordered within. I think some frameworks will need the scheduler API to support that to migrate without breaking assumptions their consumer make, and more decoupled components, like web components, don't yet have a sync centralized scheduler they could rely on.
Possible implementation strategies
Nanotask queue
One way to implement this is with another queue that's flushed before the
postTask()
call returns. Many years ago this was discussed as a "nanotask" queue. Today we have a similar queue in the custom elements reaction queue. That queue could be generalized to support this kind of task.Using a queue would sidestep the need to track task ownership and the parent/child relationships.
Ownership tracking
Another strategy is to do explicit task ownership tracking. Each task would have it's own list of child tasks and wait for them to be completed before returning.
The most powerful version of this approach would be one where the task tree can be a sparse subset of the tree of objects that own the tasks and that any set of pending tasks is run in top-first order. This is also very similar to how some framework schedulers work.
The benefit of this approach is that it can handle cross-tree updates optimally.
There are a lot of data-management patterns where multiple components may be notified of data changes. In response to those changes components update, and often propagate changes down the tree. What you want to avoid is a child updating before its parent, the the parent's update triggers a second update on the child.
This would solve the tree-aware task scheduler issue I opened in WICG/webcomponents#1055
The text was updated successfully, but these errors were encountered: