Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@watchable/unpromise

Package Overview
Dependencies
Maintainers
1
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@watchable/unpromise

Promise with unsubscribe feature that minimises memory leaks

  • 1.0.2
  • latest
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
207K
decreased by-0.07%
Maintainers
1
Weekly downloads
 
Created
Source

Unpromise: Proxy promises for unsubscription

Javascript's built-in implementation of Promise.race and Promise.any have a bug/feature that leads to uncontrollable memory leaks. See the Typical Problem Case below for reference.

The Memory leaks are fixed by using @watchable/unpromise.

In general the Promise API doesn't allow for an unsubscription model. The @watchable/unpromise package wraps individual promises to provide an unsubscribe method. It uses this approach to provide safe implementations of Unpromise.race and Unpromise.any. However, the ability to unsubscribe Promises may be useful for other cases where the Promise reference chains (and therefore memory leaks) are otherwise out of your control.

Usage

Substitute Unpromise.race or Unpromise.any in place of Promise.race and Promise.any...

import { Unpromise } from "@watchable/unpromise";

const raceResult = await Unpromise.race([taskPromise, interruptPromise]);

const anyResult = await Unpromise.any([taskPromise, interruptPromise]);

Advanced users exploring other async/await patterns should consider Unpromise.proxy() or Unpromise.resolve(). Read more at the API docs.

Install

npm install @watchable/unpromise

Import OR Require

import { Unpromise } from "@watchable/unpromise"; // esm build
const { Unpromise } = require("@watchable/unpromise"); // commonjs build

Under the hood: Step 1 - proxy

The library manages a single lazy-created ProxyPromise for you that shadows any Promise. For every native Promise there is only one ProxyPromise. It remains cached in a WeakMap for the lifetime of the Promise itself. On creation, the shadow ProxyPromise adds handlers to the native Promise's .then() and .catch() just once. This eliminates memory leaks from adding multiple handlers.

const proxyPromise = Unpromise.proxy(promise);

As an alternative if you are constructing your own Promise, you can use Unpromise to create a ProxyPromise right from the beginning...

const proxyPromise = new Unpromise((resolve) => setTimeout(resolve, 1000));

Under the hood: Step 2 - subscribe

Once you have a ProxyPromise you can call proxyPromise.then() proxyPromise.catch() or proxyPromise.finally() in the normal way. A promise returned by these methods is a SubscribedPromise. It behaves like any normal Promise except it has an unsubscribe() method that will remove its handlers from the ProxyPromise.

Under the hood: Step 3 - unsubscribe

Finally you must call subscribedPromise.unsubscribe() before you release the promise reference. This eliminates memory leaks from subscription and (therefore) from reference retention.

Under the hood: Simple Shortcuts

Using Unpromise.race() or Unpromise.any() is recommended. Using these static methods, the proxying, subscribing and unsubscribing steps are handled behind the scenes for you automatically.

Alternatively const subscribedPromise = Unpromise.resolve(promise) completes both Step 1 and Step 2 for you (it's equivalent to const subscribedPromise = Unpromise.proxy(promise).subscribe() ). Then later you can call subscribedPromise.unsubscribe() to tidy up.

Typical Problem Case

In the example app below, we have a long-lived Promise that we await every time around the loop with Promise.race(...). We use race so that we can respond to either the task result or the keyboard interrupt.

Unfortunately this leads to a memory leak. Every call to Promise.race creates an unbreakable reference chain from the interruptPromise to the taskPromise (and its task result), and these references can never be garbage-collected, leading to an out of memory error.

const interruptPromise = new Promise((resolve) => {
  process.once("SIGINT", () => resolve("interrupted"));
});

async function run() {
  let count = 0;
  for (; ; count++) {
    const taskPromise = new Promise((resolve) => {
      // an imaginary task
      setImmediate(() => resolve("task_result"));
    });
    const result = await Promise.race([taskPromise, interruptPromise]);
    if (result === "interrupted") {
      break;
    }
    console.log(`Completed ${count} tasks`);
  }
  console.log(`Interrupted by user`);
}

run();

Keywords

FAQs

Package last updated on 04 Jun 2024

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc