'Use async iterator triggered by a custom function in main scope

What I want to do is to create an iterator, which is only triggered when an external function is called, say an external event.

An iterator that simply waits for custom events.

function createIteratorWithFunction() {
  var thingThatResolves;
  var asyncIterable = {
    thingThatResolves,
    [Symbol.asyncIterator]() {
      return {
        next() {
          return (new Promise((resolve, reject) => asyncIterable.thingThatResolves = (resolve))).then(_ => ({
            value: _,
            done: false
          }));

        },
        return () {
          return {
            done: true
          }
        }
      };
    }
  };
  return asyncIterable;

}

iter = createIteratorWithFunction();
(async function() {
  for await (let val of iter) {
    console.log(val);
  }
})()
<button onclick="iter.thingThatResolves('execute');iter.thingThatResolves(3)">execute next!</button>

As you can see, it only resolves 'execute', but not 3, of course because promises can't be resolved more than once, and it only is updated asynchronously, I understand this, but since the iterator is async, how would I create a queue, so that any values that could've synchronously been triggered are retrieved by next(), as well?



Solution 1:[1]

Another idea & way of doing this, you could use custom events, one advantage is that the code is much easier to reason with.

Below I've knocked up a simple example, it also allows you to cancel the iterator & handle errors. makeIter simple gives you 4 functions,

  1. add = use this to add an item to the itterator.
  2. iter = this is the iterator you can for await on.
  3. done = if were done, you can call this and let the GC do it's thing.
  4. error = allows you to put an error into the iterator, you can test this by un-commenting the last line.

To prevent any race conditions I've simply used a stack..

function makeIter() {
  const obj = new EventTarget();
  const evName = 'my-iter'; 
  
  const stack = [];
  
  obj.addEventListener(evName, e => {
    stack.push(e.detail);
    resolve();
  }); 

  async function *iter() {
    while (true) {
      await new Promise(r => resolve = r);
      while (stack.length) {
        const s = stack.shift();
        if (s.resolve) yield(s.resolve);
        if (s.reject) throw s.reject;
        if (s.cancel) return;
      }
    }
  }
  function ev(p) {
    obj.dispatchEvent(new CustomEvent(evName, {detail:p}));
  }
  
  return {
    error: (e) => ev({reject: e}), 
    done: () => ev({cancel: true}),
    add: item => ev({resolve: item}),
    iter: iter()
  }
}




///testing...
const test = makeIter();

(async function () {
  try {
    for await (const item of test.iter) {
      console.log(item);
    }
  } finally {
    console.log('iter done');
  }
}()); 

test.add('hello');
setTimeout(() => test.add('there'), 100);
setTimeout(() => {test.add('1'); test.add('2'); test.add('3'); }, 200);
setTimeout(() => test.add('4'), 400);

setTimeout(() => test.done(), 1000);
//setTimeout(() => test.error(new Error('oops')), 1000);

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1