S-array icon indicating copy to clipboard operation
S-array copied to clipboard

debouncing

Open shimaore opened this issue 7 years ago • 1 comments

Context: I'm using S.js/SArray in a Surplus application, with a lot of async updates (via socket.io). I'm trying to par down the number of DOM updates, specifically with code that has to deal with multiple operations on SArray; my bottleneck was in code similar to:

var filtering_field = S.data('hello') // value from UI element
var filtering_function = S( (value) =>
  return value === filtering_field()
)
var original_list = SArray( [] ) // updated over socket.io
var filtered_list = S( =>   // displayed using Surplus
  return original_list.filter(filtering_function).sort().splice(0,99)
)

In this example, filtering_field is the value of a UI element, while original_list is updated over socket.io. The filtered_list is then displayed using Surplus.

I've been debouncing native values using S.js with code that looks like the following (this is more-or-less my coffeescript code with-parentheses-added for clarity):

var debounce = (src,timeout,conclude) =>
  var timer = null
  S( () =>
    var v = src()
    clearTimeout(timer)
    timer = setTimeout( ( () => conclude(v) ), timeout )
    return
  )
  return

To create a debounced signal I then use:

var debounced_signal = S.data( null )
debounce( original_signal, timeout, debounced_signal )

However this is clearly sub-optimal when using SArray:

var debounced_array = SArray( [] )
debounce( original_array, timeout, debounced_array )

because the entire SArray gets overwritten every time, which means that .filter etc. are not optimized on changed values, but are re-applied to the entire Array every time. (Assuming I understand what SArray does!!)

Is there a better way to debounce an SArray?

shimaore avatar Oct 29 '18 12:10 shimaore

Filtering is the most common scenario where I debounce signals. I'm usually debouncing the filtering_field, so that we don't re-sort until the user stops typing.

The quick answer, perhaps disappointingly, is that SArray.filter() doesn't currently leverage the previous run. It always scans the entire array, so you're not losing any efficiency with your debouncing approach. What I've called SArray's "caveman approach" -- just parallel the normal Array methods -- makes it very difficult to do better than O(N) here.

If your filter predicate is expensive (i.e. something much more than what you've shown here) you can use .map() to memoize the predicate results for each array item. OTOH, if your predicate is as simple as you've shown, the extra bookkeeping of that will likely outweigh the benefits of memoization.

If you can give me some more details about the actual app I can maybe give more targeted suggestions, including about the current implementation status of some bits of code. For instance, Surplus code that binds all the children of a parent currently tries harder to minimize DOM ops than places where only some children are bound. I.e.:

    <div>
        {array()} {/* this will produce minimum DOM ops more frequently than ... */}
    </div>
    <div>
        Some other element
        {array()} {/* ...this will */}
    </div>

Also, if feasible, using mapSequentially() can sometime produce considerably faster DOM changes, as it keeps nodes' locations static, which lets the browser leverage more of its previous layout. The non-keyed surplus implementation in js-framework-benchmark is an example of that approach.

adamhaile avatar Oct 29 '18 19:10 adamhaile