jamiemccrindle/axax

Combine iterators

benjamingr opened this issue · 7 comments

Might be nice to have some methods that lets you combine two async iterators - concat zip etc.

No, pipe combines combinators, not iterators

@benjamingr could you provide an example usage?

Some setup:

function once(target, eventName) {
  return new Promise(resolve => {
    function fn(e) { resolve(e); target.removeEventListener(e); }
    target.addEventListener(fn);
  });
}
// get a stream for clicks on an element
async function* clicks(target) {
   while(true) yield once(target, 'click');
}

// take n items from an iterator and then close it
async function* take(iterator, n) {
  for(let i = 0; i < n; i++) {
    const {done, value} = await iterator.next();
    if (done) return value;
    yield value;
  }
  iterator.return(); // signal done to the stream
}

Now for our combinators:

const clicksOnHeader = clicks(document.querySelector('header'));
const clicksOnFooter = clicks(document.querySelector('footer'));
// let's see some operators we might want to end
const clicksOnEitherHeaderOrFooter = merge(
  clicksOnHeader, 
  clicksOnFooter
);

const twoHeaderThenTwoFooter = concat(
  take(clicksOnHeader, 2),
  take(clicksOnFooter, 2)
);

for await(const e of clicksOnEitherOrFooter) {
  // triggers whenever the header or footer are clicked
}
for await(const e of twoHeaderThenTwoFooter) {
  // values: first two clicks on header and then two clicks on footer then close
} 

Thanks!

Sure! On a completely unrelated note - We are also gathering usage examples for async-iterators at Node.js core (here) and are working on giving users a better experience (here and here).

If you ever notice a case where the Node.js (or V8) behaviour is confusing or problematic please do bring it up so we can improve it. If you have any questions please do reach out as well (via email or on one of the above repos).

I also implemented some combinators "back then" and Kevin (who was proposal champion at the time) even made it run - it's quite old at this point though since it's from when the proposal was at an early stage - I might have updated it at some point :)

Great. I'll feed back anything I find that could lead to a better experience.

I think you can do what you need with the current combinators (I added take). The logic is the same (I think) I just changed a few things:

  • using built in fromEvent
  • using built intake
  • split out the two cases or they would close each other's fromEvent clicks
  • concat syntax is slightly different (currying as opposed to args)
  • (used typescript... but that's not relevant)
import { fromEvent } from "axax/es5/fromEvent";
import { concat } from "axax/es5/concat";
import { merge } from "axax/es5/merge";
import { take } from "axax/es5/take";

async function twoClicks() {
  const clicksOnHeader = fromEvent(
    document.querySelector(".header") as HTMLElement,
    "click"
  );
  const clicksOnFooter = fromEvent(
    document.querySelector(".footer") as HTMLElement,
    "click"
  );

  const twoHeaderThenTwoFooter = concat(take(2)(clicksOnHeader))(
    take(2)(clicksOnFooter)
  );

  for await (const e of twoHeaderThenTwoFooter) {
    // values: first two clicks on header and then two clicks on footer then close
    console.log("clicked on two of header and footer");
  }
}

async function headerOrFooter() {
  const clicksOnHeader = fromEvent(
    document.querySelector(".header") as HTMLElement,
    "click"
  );
  const clicksOnFooter = fromEvent(
    document.querySelector(".footer") as HTMLElement,
    "click"
  );

  // let's see some operators we might want to end
  const clicksOnEitherHeaderOrFooter = merge(clicksOnHeader, clicksOnFooter);


  for await (const e of clicksOnEitherHeaderOrFooter) {
    // triggers whenever the header or footer are clicked
    console.log("clicked on either header or footer");
  }
}


document.addEventListener('DOMContentLoaded', () => {
  twoClicks();
  headerOrFooter();
})