JavaScript proxies : aggregate notifications after array sort

56 Views Asked by At

I would like to find a way to get only one notification after an array sort

Is there a way? Thank you

const callback = function () {
  console.log (...arguments)
}
const array = [2,1]
const handler = {
  set (target, prop, value, receiver) {
    callback (prop, value)
    return Reflect.set (target, prop, value, receiver)
  }
}
const proxy = new Proxy (array, handler)
proxy.sort()
// calls two times callback
// prints "0" 1 and "0" 2
// would like one notification : "array sorted"
2

There are 2 best solutions below

2
InSync On BEST ANSWER

You can use the .apply() trap on Array#sort() itself:

console.config({ maximize: true });

Array.prototype.sort = new Proxy(Array.prototype.sort, {
  apply(target, thisArg, argumentList) {
    const sortedThis = Reflect.apply(target, thisArg, argumentList);
    console.log('sorted');
    return sortedThis;
  }
});

console.log([2, 1].sort());
<script src="https://gh-canon.github.io/stack-snippet-console/console.min.js"></script>

...or, if you want the proxy to affect your array only, you can return a custom function if the property being accessed is 'sort':

console.config({ maximize: true });

const proxy = new Proxy([2, 1], {
  get(target, property, receiver) {
    if (property === 'sort') {
      return function(comparer) {
        const originalSort = Reflect.get(target, property, receiver);
        const sortedThis = originalSort.apply(target, [comparer]);
        
        console.log('sorted');
        
        return sortedThis;
      }
    }
    
    return Reflect.get(target, property, receiver);
  }
});

console.log('.sort() works:', proxy.sort());
console.log('.slice() is fine too:', proxy.slice(1));
<script src="https://gh-canon.github.io/stack-snippet-console/console.min.js"></script>

3
Peter Seliger On

A viable approach which does not utilize a proxy was to use a generic implementation of an around method-modifier. The latter can be seen as a specialized case of function-wrapping.

Such a modifier accepts two functions, proceed and handler as well as a target-object as its 3 parameters. It does return a function which again is going to return the result of the handler function. The latter does get invoked within the context of the (optionally) provided target while also getting passed the proceed-function, its own handler-reference and the modified function's arguments-array.

Thus, based on such a modifier, the OP could achieve the expected behavior by modifying e.g. an array instance's sort-method like this ...

const arr = [2, 1];

// reassign the array instance's specifically modified `sort`.
arr.sort = around(arr.sort, notifyAboutFinishedTask, arr);

... where notifyAboutFinishedTask is the handler function which implements exactly what the OP is looking for ...

"... only one notification after an array sort"

... Example code ...

// implementation of `sort`-specific `around`-handler.
function notifyAboutFinishedTask(proceed, handler, args) {
  const arr = this;

  // original task.
  proceed.apply(arr, args);

  // additional notification.
  console.log('\narray sorted');
  console.log('arr ...', arr);
  console.log('arguments ...', args);
}
const arr = [2, 1];

console.log('unsorted arr ...', arr);

// reassign the array instance's specifically modified `sort`.
arr.sort = around(arr.sort, notifyAboutFinishedTask, arr);

// invoking sort twice, once with and once without sort function.
arr.sort();
arr.sort((a, b) => b - a);
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>

// implementation of an `around` modifier.
function around(proceed, handler, target) {
  return function (...args) {

    return handler.call(target ?? null, proceed, handler, args);
  };
}

</script>