0

I'm attempting to define a function that returns a promise. The promise should resolve when a given array is set (push()).

To do this I'm attempting to use a Proxy object (influenced by this):

let a = []

;(async function(){

  const observe = array => new Promise(resolve =>
      new Proxy(array, {
          set(array, key, val) {
              array[key] = val;
              resolve();
            }
      }));

  while(true){

      await observe(a);
      console.log(new Date().toLocaleTimeString(),"Blimey Guv'nor:",`${a.pop()}`);
    }

})(a);

;(async function(){
    await new Promise(resolve => timerID = setTimeout(resolve, 2000))
    a.push('ʕ·͡ᴥ·ʔ');
    a.push('¯\(°_o)/¯ ')
})(a)

I can't see why this doesn't work. Does anyone have any idea?

More generally, what is a good way to have a promise resolve on push to an array?

Lee
  • 29,398
  • 28
  • 117
  • 170
  • You can take a look at the following answer https://stackoverflow.com/questions/55309894/resolve-promise-or-add-callback-after-array-push-method – Avior Jan 20 '20 at 10:55
  • Thanks @Avior, despite its title that answer is about promises on a `fetch` command – Lee Jan 20 '20 at 10:59
  • 1
    [Properly building Javascript proxy set handlers for arrays](https://stackoverflow.com/questions/45528463/properly-building-javascript-proxy-set-handlers-for-arrays) – Andreas Jan 20 '20 at 11:04
  • I don't think `await observe(a);` will ever resolve... – evolutionxbox Jan 20 '20 at 11:08
  • @evolutionxbox thanks I agree, but why not? – Lee Jan 20 '20 at 11:09
  • 1
    `;(async function() { ... })(a);` - Passing `a` into the IIFE but not using it is useless. And you would have to store the result of `new Proxy()` as `a` – Andreas Jan 20 '20 at 11:14
  • Thanks @Andreas, am I not using it in `observe(a)`? – Lee Jan 20 '20 at 11:17
  • Firstly `while(true)` is infinitely looping. I can't see how it would ever get to the `a.push` lines. – evolutionxbox Jan 20 '20 at 11:17
  • Plus promises are only supposed to resolve once. – Ben Fortune Jan 20 '20 at 11:17
  • It's also going to wait for `observe` to resolve before it moves on to `a.push`, but since `a.push` is what resolves it, it will never resolve. – evolutionxbox Jan 20 '20 at 11:25
  • @evolutionxbox if you replaced the observe() statement with `new Promise(resolve => timerID = setTimeout(resolve, 5000))` it checks the array every 5s (and would run). But I want it to only do this on array push. – Lee Jan 20 '20 at 11:25
  • @BenFortune Thanks - I don't think I've misunderstood this - is there an obvious mistake? – Lee Jan 20 '20 at 11:27
  • As @BenFortune has stated, once a promise is resolved it can't be resolved again. Consider creating promises on each push, rather than once at the proxy creation. – evolutionxbox Jan 20 '20 at 11:27
  • @evolutionxbox my intention is that calling `observe(a)` creates a new promise. This should be then resolved in the new proxy object, when a is pushed to... A new promise is created every loop. – Lee Jan 20 '20 at 11:30
  • That isn't possible _in its current form_ due to the reasons I've already described. – evolutionxbox Jan 20 '20 at 11:32
  • Why do you need it to be a promise? – evolutionxbox Jan 20 '20 at 11:46
  • @evolutionxbox it needs to be accepted by the `await` statement – Lee Jan 20 '20 at 11:48
  • That's a technical restriction. I'm asking _why_. – evolutionxbox Jan 20 '20 at 11:49
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/206278/discussion-between-atomh33ls-and-evolutionxbox). – Lee Jan 20 '20 at 11:52
  • Sorry I couldn't help. – evolutionxbox Jan 20 '20 at 13:52
  • @evolutionxbox no problem, thanks for taking the time to have a look – Lee Jan 20 '20 at 14:01

1 Answers1

3

The problems with your attempt:

  • you invoke .push on the original array, not the proxied one. Where you create the proxy, it is returned to no-one: any reference to it is lost (and will be garbage collected).
  • The code following after the line with await will execute asynchronously, so after all of your push calls have already executed. That means that console.log will execute when the array already has two elements. Promises are thus not the right tool for what you want, as the resolution of a promise can only be acted upon when all other synchronous code has run to completion. To get notifications during the execution synchronously, you need a synchronous solution, while promises are based on asynchronous execution.

Just to complete the answer, I provide here a simple synchronous callback solution:

function observed(array, cb) {
    return new Proxy(array, {
        set(array, key, val) {
            array[key] = val;
            if (!isNaN(key)) cb(); // now it is synchronous
            return true;
        }
    });
}

let a = observed([], () =>
    console.log(new Date().toLocaleTimeString(),"Blimey Guv'nor:", `${a.pop()}`)
);

a.push('ʕ·͡ᴥ·ʔ');
a.push('¯\(°_o)/¯ ');

As noted before: promises are not the right tool when you need synchronous code execution.

When each push is executed asynchronously

You can use promises, if you are sure that each push happens in a separate task, where the promise job queue is processed in between every pair of push calls.

For instance, if you make each push call as part of an input event handler, or as the callback for a setTimeout timer, then it is possible:

function observed(array) {
    let resolve = () => null; // dummy
    let proxy = new Proxy(array, {
        set(array, key, val) {
            array[key] = val;
            if (!isNaN(key)) resolve();
            return true;
        }
    });
    proxy.observe = () => new Promise(r => resolve = r);
    return proxy;
}


let a = observed([]);
(async () => {
    while (true) {
        await a.observe();
        console.log(new Date().toLocaleTimeString(),"Blimey Guv'nor:",`${a.pop()}`);
    }
})();

setTimeout(() => a.push('ʕ·͡ᴥ·ʔ'), 100);
setTimeout(() => a.push('¯\(°_o)/¯ '), 100);
trincot
  • 317,000
  • 35
  • 244
  • 286
  • Great points. The first really helped me understand my error. re. the second - I was not clear in the question (the array pushes should have been via the console, or after a delay). I'll adjust the question, thanks. – Lee Jan 20 '20 at 13:13
  • 2
    After your edit, the two `push` calls are still executed in the same synchronous piece of code (even though that piece itself executed asynchronously). There is no way a promise resolution would get in-between those two `push` calls. They really need to be each in a separate asynchronous job for a promise to have any chance to trigger a job in-between. – trincot Jan 20 '20 at 13:20
  • See addition to answer. – trincot Jan 20 '20 at 13:29
  • 1
    Superb! In my use case, many of the push calls will indeed be asynchronous (again should have made that clearer). Thanks. – Lee Jan 20 '20 at 14:01