top | item 47181953

(no title)

bikeshaving | 2 days ago

A long time ago, I wrote an abstraction called a Repeater. Essentially, the idea behind it is, what would the Promise constructor look like if it was translated to async iterables.

  import { Repeater } from "@repeaterjs/repeater";
  
  const keys = new Repeater(async (push, stop) => {
    const listener = (ev) => {
      if (ev.key === "Escape") {
        stop();
      } else {
        push(ev.key);
      }
    };
    window.addEventListener("keyup", listener);
    await stop;
    window.removeEventListener("keyup", listener);
  });
  const konami = ["ArrowUp", "ArrowUp", "ArrowDown", "ArrowDown", "ArrowLeft", "ArrowRight", "ArrowLeft", "ArrowRight", "b", "a"];
  (async function() {
    let i = 0;
    for await (const key of keys) {
      if (key === konami[i]) {
        i++;
      } else {
        i = 0;
      }
      if (i >= konami.length) {
        console.log("KONAMI!!!");
        break; // removes the keyup listener
      }
    }
  })();
https://github.com/repeaterjs/repeater

It’s one of those abstractions that’s feature complete and stable, and looking at NPM it’s apparently getting 6.5mil+ downloads a week for some reason.

Lately I’ve just taken the opposite view of the author, which is that we should just use streams, especially with how embedded they are in the `fetch` proposals and whatever. But the tee critique is devastating, so maybe the author is right. It’s exciting to see people are still thinking about this. I do think async iterables as the default abstraction is the way to go.

discuss

order

pcthrowaway|2 days ago

In the repeater callback, you're both calling the stop argument and awaiting it. Is it somehow both a function and a promise? Is this possible in JS?

edit: I found where stop is created[1]. I can't say I've seen this pattern before, and the traditionalist in me wants to dislike the API for contradicting conventions, but I'm wondering if this was designed carefully for ergonomic benefits that outweigh the cost of violating conventions. Or if this was just toy code to try out new patterns, which is totally legit also

[1]: https://github.com/repeaterjs/repeater/blob/638a53f2729f5197...

bikeshaving|2 days ago

Yes, the callable promise abstraction is just a bit of effort:

  let resolveRef;
  const promise = new Promise((res) => { resolveRef = res; });
  
  const callback = (data) => {
    // Do work...
    resolveRef(data); // This "triggers" the await
  };

  Object.assign(callback, promise);

There’s a real performance cost to awaiting a fake Promise though, like `await regularPromise` bypasses the actual thenable stuff.

sheept|1 day ago

It's not common, but there are web APIs where you await a promise that already exists as a property, rather than being returned from a function call, like

    await document.fonts.ready

    device.lost.then(() => {
      console.log('WebGPU device lost :(')
    })
I feel like this isn't confusing if you know how promises work, but maybe it can be confusing for someone coming from Python/Rust, where async functions don't evaluate until their futures are awaited.

boilerupnc|2 days ago

Off topic - But just wanna say - Love the cheat code! 30 Lives added :-) Nostalgia runs deep with that code. So deep - in fact, that I sign many of my emails off with "Sent by hitting Up, Up, Down, Down, Left, Right, Left, Right, B, A"

sfink|2 days ago

Off topic to the off topic, but that logic doesn't look right. It seems like if up is pressed, you might need to reset i to 1 or 2, not 0.