Détail du package

@repeaterjs/repeater

repeaterjs18.1mMIT3.0.6

The missing constructor function for creating safe async iterators

readme

Repeater.js

The missing constructor for creating safe async iterators.

For more information, visit repeater.js.org.

Installation

Repeater.js is available on npm in the CommonJS and ESModule formats.

$ npm install @repeaterjs/repeater

$ yarn add @repeaterjs/repeater

Requirements

The core @repeaterjs/repeater module has no dependencies, but requires the following globals in order to work:

  • Promise
  • WeakMap
  • Symbol
    • Symbol.iterator
    • Symbol.asyncIterator

In addition, repeaters are most useful when used via async/await and for await…of syntax. You can transpile your code with babel or typescript to support enviroments which lack these features.

Examples

Logging timestamps with setInterval

import { Repeater } from "@repeaterjs/repeater";

const timestamps = new Repeater(async (push, stop) => {
  push(Date.now());
  const interval = setInterval(() => push(Date.now()), 1000);
  await stop;
  clearInterval(interval);
});

(async function() {
  let i = 0;
  for await (const timestamp of timestamps) {
    console.log(timestamp);
    i++;
    if (i >= 10) {
      console.log("ALL DONE!");
      break; // triggers clearInterval above
    }
  }
})();

Creating a repeater from a websocket

import { Repeater } from "@repeaterjs/repeater";

const socket = new WebSocket("ws://echo.websocket.org");
const messages = new Repeater(async (push, stop) => {
  socket.onmessage = (ev) => push(ev.data);
  socket.onerror = () => stop(new Error("WebSocket error"));
  socket.onclose = () => stop();
  await stop;
  socket.close();
});

(async function() {
  for await (const message of messages) {
    console.log(message);
    if (message === "close") {
      console.log("Closing!");
      break; // closes the socket
    }
  }
})();

socket.onopen = () => {
  socket.send("hello"); // "hello"
  socket.send("world"); // "world"
  socket.send("close"); // "close", "Closing!"
};

Listening for the Konami Code and canceling if <kbd>Escape</kbd> is pressed

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
    }
  }
})();

Converting an observable to an async iterator

import { Subject } from "rxjs";
import { Repeater } from "@repeaterjs/repeater";

const observable = new Subject();
const repeater = new Repeater(async (push, stop) => {
  const subscription = observable.subscribe({
    next: (value) => push(value),
    error: (err) => stop(err),
    complete: () => stop(),
  });
  await stop;
  subscription.unsubscribe();
});

(async function() {
  try {
    for await (const value of repeater) {
      console.log("Value: ", value);
    }
  } catch (err) {
    console.log("Error caught: ", err);
  }
})();
observable.next(1);
// Value: 1
observable.next(2);
// Value: 2
observable.error(new Error("Hello from observable"));
// Error caught: Error: Hello from observable

changelog

Changelog

repeater@3.0.6 - 2024-05-08

  • Fix promise returned from push function not resolving when using a buffer.

    repeater@3.0.5 - 2023-11-07

    Fixed

  • Add export maps to package.json files for the TypeScript nodenext

    repeater@3.0.4 - 2020-09-02

    Fixed

  • Fixed memory leaks in static combinator functions race, merge, zip and latest due to Promise.race.

    repeater@3.0.3 - 2020-08-03

    Fixed

  • Fixed missing file in package.json.

    repeater@3.0.2 - 2020-08-03

    Fixed

  • Fixed throw method not catching promises in newer versions of v8 (node.js 12+).
  • Added ESM support for newer versions of node.

    limiters@0.3.3 - 2019-10-13

    Fixed

  • build files with target set to es5 rather than esnext

    timers@0.3.3 2019-11-10

    Fixed

  • build files with target set to es5 rather than esnext

    pubsub@0.3.4 2019-11-10

    Fixed

  • build files with target set to es5 rather than esnext

    repeater@3.0.1 2019-11-10

    Fixed

  • build files with target set to es5 rather than esnext

    limiters@0.3.2 - 2019-10-13

    Changed

  • upgrade dependencies

    timers@0.3.2 - 2019-10-13

    Changed

  • upgrade dependencies

    pubsub@0.3.3 - 2019-10-13

    Changed

  • upgrade dependencies

    repeater@3.0.0 2019-11-10

    This release overhauls the push and stop promises and the return and throw methods to be more in line with the behavior of async generators. If you used repeaters to simply add and remove event handlers, you can upgrade without worrying about breaking changes. However, if you awaited push calls, read the value of the stop promise, or used the return or throw methods in non-trivial ways, this release may break your code.

Changed

  • Previously, the n-th call to push resolved to the value passed to the n-th call to Repeater.prototype.next. This was inaccurate insofar as async generators resume the n-th yield with the value passed to the n+1-th call to next. Additionally, async generators completely ignore the first value passed to next entirely. The former behavior of repeaters was confusing insofar as the first call to push would always resolve immediately, because push could not be called until next was called for the first time. In 3.0, the behavior has now been changed so that the n-th call to push resolves to the n+1-th call to next.

    For the most part, code which awaits push will work as expected, except when the code relied on the first call to push resolving immediately. To make sure you’re not relying on the old behavior of push, you should make sure that you await push at the bottom of loops, rather than the top. See the changes to timers as an example.

  • Calling Repeater.prototype.throw will now cause the previous push call to reject, rather than simply stopping the repeater and throwing the error. This means errors thrown into repeaters are now recoverable. If you call push without awaiting or calling then/catch on the returned promise and then call the throw method, the repeater will rethrow the error, preserving the previous behavior. However, if you await push or otherwise call the then or catch methods, it becomes your responsibility to handle errors thrown in via throw and stop the repeater. #43

  • The stop promise no longer resolves to the value passed to Repeater.prototype.return. Instead, it consistently fulfills to undefined when the repeater is stopped. Additionally, calling Repeater.prototype.return will return a result whose value is always whatever was passed to return. In the case of premature returns, the return value of the executor is ignored unless it throws an error. #41

    This change was made because there is no way with async generators to inspect the value passed to return from within the async generator. Additionally, the only way to modify the result returned from the return method would be to put a return statement in a finally block within the async generator, which is typically considered to be a bad practice. Repeaters now uniformly return the value passed to the return method. See #40 for a longer explanation for why we made this change.

  • Errors thrown by the executor now take priority over rejections passed to push. If a pushed promise rejects, the repeater will await the execution result and throw an error thrown by the executor instead of the rejected promise.

  • Changes to combinator methods

    • Improved type inference for Repeater.race and Repeater.merge
    • Repeater.merge yields non-iterable values, so you can call merge on an iterable of promises to get an async iterator which produces values as each promise fulfills.
    • Iterables passed to combinator methods are upgraded to async iterators, so that promise-like values are awaited and unwrapped before being pushed to the repeater
  • Changes to typescript typings:

    • Repeater, RepeaterExecutor, Push and other related types now take the type parameters TReturn and TNext in line with typescript 3.6’s strict async generator typings.
    • The RepeaterBuffer interface and concrete classes no longer accept a type parameter. All places where the type parameter would have been used, unknown is used instead. You should never directly add or remove values from buffers.
    • The typings of the next and return methods have been changed to accept PromiseLike<T> and PromiseLike<TReturn> respectively as parameters.

      pubsub@0.3.2 - 2019-10-13

      Fixed

  • Fixed build error in @repeaterjs/pubsub

    timers@0.3.1 - 2019-06-29

    Fixed

  • Fixed timers using the wrong version of @repeaterjs/repeater

    limiters@0.3.1 - 2019-06-29

    Fixed

  • Fixed limiters using the wrong version of @repeaterjs/repeater

    pubsub@0.3.1 - 2019-06-29

    Fixed

  • Fixed pubsub using the wrong version of @repeaterjs/repeater.

    timers@0.3.0 - 2019-08-24

    Changed

  • Renamed all instances of Channel to Repeater (e.g. Channel to Repeater, ChannelBuffer to RepeaterBuffer). See #18 for the rationale behind this name change.

    limiters@0.3.0 - 2019-08-24

    Changed

  • Renamed all instances of Channel to Repeater (e.g. Channel to Repeater, ChannelBuffer to RepeaterBuffer). See #18 for the rationale behind this name change.

    pubsub@0.3.0 - 2019-08-24

    Changed

  • Renamed all instances of Channel to Repeater (e.g. Channel to Repeater, ChannelBuffer to RepeaterBuffer). See #18 for the rationale behind this name change.

    repeater@2.0.0 - 2019-08-24

    Changed

  • Renamed all instances of Channel to Repeater (e.g. Channel to Repeater, ChannelBuffer to RepeaterBuffer). See #18 for the rationale behind this name change.

    timers@0.2.1 - 2019-06-09

  • Fixed timers using the wrong version of @channel/channel.

    limiters@0.2.1 - 2019-06-09

  • Fixed limiters using the wrong version of @channel/channel.

    pubsub@0.2.1 - 2019-06-09

    Fixed

  • Fixed pubsub using the wrong version of @channel/channel.

    channel@1.0.0 - 2019-06-09

    Added

  • The Channel class now exposes the static methods Channel.race, Channel.merge, Channel.zip and Channel.latest #4.

    Changed

  • The close and stop arguments passed to executor have been merged as the second argument.
  • The stop promise resolves to argument passed to return if return is called.
  • The push function now resolves to the value passed to next.
  • The push function now unwraps promise-like values passed to push.
  • Buffers now throw an error if remove is called when the buffer is empty.
  • Channel properties and methods which don’t belong to the async iterator interface are now hidden using a private WeakMap.
  • Channels stop immediately when the executor throws an error.
  • Executor now runs synchronously when next is called for the first time (#10).
  • The final iteration result/errors are now consumed by iterator methods.
  • return/throw behave more like the methods do for async generators.

    timers@0.2.0 - 2019-06-09

    Changed

  • delay now returns an channel which can be reused
  • timeout returns a channel

    limiters@0.2.0 - 2019-06-09

    Added

  • throttler can now be passed a cooldown boolean option which forces the channel to wait before yielding the final token.

    Changed

  • semaphore and throttler now both return channels rather than async iterators.
  • throttler function now takes options instead of a number representing limit as the second arg.
  • semaphore and throttler will throw a RangeError if limit is less than 1.

    Fixed

  • throttler now uses a sliding window to limit #1.

    pubsub@0.2.0 - 2019-06-09

    Changed

  • Type definitions have changed slightly.

    channel@0.1.1 - 2019-05-06

    Added

  • Adds throw method to channels.

    timers@0.1.1 - 2019-04-08

    limiters@0.1.1 - 2019-04-08

    pubsub@0.1.1 - 2019-04-08

    channel@0.1.0 - 2019-04-08

  • Initial release.

    timers@0.1.0 - 2019-04-08

  • Initial release.

    limiters@0.1.0 - 2019-04-08

  • Initial release.

    pubsub@0.1.0 - 2019-04-08

  • Initial release.