Configure lazy behavior of watch callback on resume
#708
8ctavio
started this conversation in
RFC Discussions
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
>3.6Summary
Introduction of new
watchoption to configure the eager/lazy behavior ofwatchupon calling.resume(), that is, whether resuming a paused watcher runs/schedules its callback if its dependencies were triggered while it was paused. Currently, the watcher callback will always be executed/scheduled if dependencies changed (eager behavior).Basic example
Motivation
A key feature of
watchis that its sources and callback function are handled separately. As such, whenwatchis called, it is not required to run the provided callback. Theimmediateoption allows to configure the eager behavior of the callback when the watcher is first set up; if set totrue,immediatecauses the callback to be (eagerly) executed upon callingwatch.An API to pause and resume watchers was introduced in version 3.5. With it, if a paused watcher's sources change, the watcher's callback will be executed once the watcher is resumed. This is partly required to properly keep track of dependencies. However, the ability to configure the callback's behavior is lost. As mentioned before, an advantage of
watchis that sources and callback are independent. Thus, it should also be useful to configure whether theresumemethod executes/schedules the callback itself.Currently, it could be said that a watcher's callback executes eagerly on resume; if its laziness could be configured, the pause and resume methods could be used to have more precise control over when may a watcher callback be executed. In particular, pause and resume could be used to fully prevent the execution of the callback while the watcher is paused. Without the lazy behavior, the callback may or may not execute on resume. With lazy behavior, resuming becomes more similar to first setting up a watcher; it indicates that, from that point on, the callback should be executed/scheduled when a dependency updates. There is a blog article showing a particular use case for this behavior.
Although it is possible to achieve the desired behavior with current public APIs (see Alternatives), some overhead is introduced since non-sync watchers require an additional synchronous watcher.
A note on paused-watcher behavior
Detailed design
A
watchcallback is executed by theWatcherEffect.runmethod. Initially, this method runs the effect to keep track of dependencies. Then, some checks are performed to skip callback execution if required, e.g., on the first run of a watcher withimmediate: false. In order to prevent callback execution when a watcher is resumed, a similar check could be added as depicted below:The objective would then be to inform
WatcherEffect.runthat it is being called becauseresumewas called. For this, the first thing to note is that a watcher'sresumemethod is defined byReactiveEffect.resumewhich calls the effect'snotifymethod. In turn,WatcherEffect.notifyorRenderWatcherEffect.notifyrespectively call or schedule calling theWatcherEffect.runmethod.For
WatcherEffectand synchronousRenderWatcherEffect, a flag could be introduced forWatcherEffect.runto detect the watcher is being resumed. This flag could be defined on theEffectFlagsenum:To support this flag,
ReactiveEffect.resumewould become:And the previous
WatcherEffect.runcheck becomes:To make this behavior configurable, a
WatchOptionsconfiguration option may be introduced. For this discussion, let the new option belazyResume:For non-synchronous
RenderWatcherEffect, theEffectFlags.RESUME_RUNis not enough since it is synchronously reset. Instead,RenderWatcherEffect.notifyshould be the one detectingRESUME_RUNand then informing the scheduled job that it was scheduled due toresumebeing called; in that case, the job would need to upadteRESUME_RUNjust likeReactiveEffect.resume.The communication between
RenderWatcherEffect.notifyandRenderWatcherEffect.jobcould be made with another flag; this time, consider a privateRenderWatcherEffect.#scheduledOnResumeproperty.RenderWatcherEffect.notifyturns on the flag when it is called withRESUME_RUN, andRenderWatcherEffect.jobupdatesRESUME_RUNifRenderWatcherEffect.#scheduledOnResumeis set:However, any
RenderWatcherEffect.notifycall after it has been called withRESUME_RUNshould unset#scheduledOnResumeso that source mutations afterresumedo not skip the callback:For this, a third
undefinedstate could be added to#scheduledOnResume:and
RenderWatcherEffect.jobshould reset#scheduledOnResumetoundefined:Drawbacks
Alternatives
The target functionality can be achieved with the public API only. See, for example, the two following projects' solutions:
watchIgnorablewatchControlledThese alternatives, however, introduce overhead since an additional sync watcher is required for non-sync watchers. See their implementations:
Unresolved questions
As previously mentioned,
RenderWatcherEffect.notifyshould reset#scheduledOnResumefor dependency updates performed afterresumeto normally execute the watcher callback. However,#scheduledOnResumealone is not enough if, in addition, dependencies are triggered before callingresume:Source updates performed while the watcher is paused, "mark" the effect as notified, so following synchronous updates do not call
notifyagain. Apart from setting#scheduledOnResumetotrue,RenderWatcherEffect.notifyshould also ensure subsequent synchronous source updates are able to notify the effect once more.For illustration, I was able to achieve this by turning off the effect's
Pendingflag:However, I do not know enough about the reactivity system to tell whether this is sufficient and appropriate.
Beta Was this translation helpful? Give feedback.
All reactions