As a rule, the depth of dependencies remains relatively small, not exceeding a couple of dozen states.
But sometimes deps can grow to indecent depths. This is especially true for applications where the user himself can control who depends on whom and how. Typical examples: spreadsheet or Gantt chart.
And not all reactivity models will allow you to implement this. And sometimes you only find out about it when it’s too late to change horses. And the crutching begins. So let's take a closer look at this aspect...
🚧 Limit: Limited by a constant
🗻 Stack: Limited by stack
🌌 Heap: Unlimited
🚧Limit
Some libraries deal with circular dependencies by introducing a limit on the number of recalculations at a time. Usually this is a dozen or two recounts.
for( let i = 0; i < MAX_REPEATS; ++i ) {
if( !dirty ) return
changeDetection()
}
throw new Error( 'Too many change detection repeats' )
This prevents the application from freezing completely. But it also fundamentally limits the depth of dependencies. It will be painful to implement a spreadsheet in such conditions.
🗻Stack
The situation is slightly better with reactivity models, where there are no artificial restrictions. However, they initiate some calculations inside others, which leads to the growth of the stack.
first() {
this.second()
}
second() {
this.third()
}
thisrd() {
this.etc()
}
And since the stack size is not infinite, it is only sufficient for a depth of several thousand states. This may already be enough for even average spreadsheets. However, if you go beyond the stack, and that’s it, an exception is thrown.
Moreover, it may or may not fly out, depending on the order in which the recalculations took place. That is, we also get instability of behavior. For example, this can manifest itself like this: when you open an application, everything is fine, but as soon as you change one state, the recalculation of a state that is deeply dependent on it drops.
However, the advantage of this approach is that the stack shows in what order the recalculation was performed, which can be useful for debugging.
🌌 Heap
The best option does not grow the stack, which allows it to work with dependencies of arbitrary depth. Well, how much RAM is enough, of course.
while( reactions.length ) {
reactions.shift().execute()
}
Unfortunately, here stack traces become less informative. But logging can come to the rescue when debugging, which, if desired, can even be pasted into the stack trace manually.
When choosing a reactive library, check in advance how it works with truly deep dependencies.
Top comments (0)