npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

promisified-resource-pool

v1.0.2

Published

A utility to manage concurrent access to a limited pool of resources

Downloads

136

Readme

promisified-resource-pool: A utility to manage promise concurrency

Overview

While JavaScript provides some basic mechanisms (Promise.all, Promise.race, etc.) to manage promise execution concurrency, it lacks a utility to constrain Promise execution to a finite limit. This package implements this mechanism through a generalization of this problem by modeling a concept of a finite pool of resources which much be leased out to each promise execution. Alongside this, it provides a means of prioritizing and aborting enqueued work, enabling a variety of useful concurrency management practices.

Usage

Warning Callbacks provided to a sizedPool must have a unique object identity.

For cases where there is no resource to manage, and the only desire is for a concurrency limit there's a sizedPool helper:

import { sizedPool } from "promisified-resource-pool";

const enqueue = sizedPool(10);

// `enqueue` can be called as desired, but only 10 callbacks will ever execute simultaneously.
// Note that the `enqueue` function returned from `sizedPool` does not accept a prioritization and
// cannot be provided an AbortSignal.
await enqueue(async () => {
  /* ... */
});

For cases with an underlying set of object resources we need to restrict concurrent access to, the resourcePool export gives more control:

Warning The second parameter provided to a resourcePool's enqueue function must have a unique object identity.

import { resourcePool } from "promisified-resource-pool";

// Create a resource pool
const enqueue = resourcePool(["resource1", "resource2"]);

// Enqueue a resource-consuming callback. The callback will be executed as soon as a resource is
// available for the callback.
const callback = async (resource) => {
  // Use the resource. The resource will be returned to the pool once the callback is complete.
};
await enqueue(null, callback, callback);

Prioritized callbacks:

import { resourcePool } from "promisified-resource-pool";

// When creating a pool, a comparator can be provided to evaluate the prioritization of enqueued
// callbacks. I've chosen to use a number here, but any stably comparable type will do.
const enqueue = resourcePool(["resource1", "resource2"], (a, b) => b.priority - a.priority);

// The priority here is specified with the second parameter to `enqueue`.
await Promise.all([
  // This callback (#1) will be immediately executed, as the pool has two resources.
  enqueue(null, { priority: 1 }, async (resource) => {
    /* ... */
  }),
  // This callback (#2) will also be immediately executed, but with the second resource.
  enqueue(null, { priority: 1 }, async (resource) => {
    /* ... */
  }),
  // This callback (#3) will be enqueued, as both resources are currently in use.
  enqueue(null, { priority: 1 }, async (resource) => {
    /* ... */
  }),
  // This callback (#4) will also be added to the queue, bit with a higher priority it will be
  // bumped ahead of callback #3 in eventual execution order.
  enqueue(null, { priority: 2 }, async (resource) => {
    /* ... */
  }),
]);

There's also the semaphore export, which constructs a "pool" that is useful in implementing pools like the sizedPool above, but in a more configurable way that's more efficient than an array:

import { resourcePool, semaphore } from "promisified-resource-pool";

// Here we can create a resource pool with a fixed size that retains prioritization capabilities.
const enqueue = resourcePool(semaphore(4), (a, b) => b.priority - a.priority);

await Promise.all([
  enqueue(null, { priority: 1 }, async () => {
    /* ... */
  }),
  enqueue(null, { priority: 2 }, async () => {
    /* ... */
  }),
]);

Abortable callbacks:

import { resourcePool } from "promisified-resource-pool";

const enqueue = resourcePool(["resource1", "resource2"]);

const abortController = new AbortController();

// To describe the behavior more succinctly I've avoided awaiting these `enqueue`s, but in practice
// It would be necessary to await them to avoid an unhandled promise rejection.

// This callback (#1) will be immediately executed, as the pool has two resources.
const callback1 = async (resource) => {
  /* ... */
};
enqueue(abortController.signal, callback1, callback1);
// This callback (#2) will also be immediately executed, as the pool has a remaining resource.
const callback2 = async (resource) => {
  /* ... */
};
enqueue(abortController.signal, callback2, callback2);
// This callback (#3) will be enqueued, as the resource pool is fully in use.
const callback3 = async (resource) => {
  /* ... */
};
enqueue(abortController.signal, callback3, callback3);

// This will cause callback #3 to be aborted and removed from the queue.
abortController.abort();

try {
  // Additional enqueued calls with this AbortSignal will immediately reject.
  const callback4 = async (resource) => {
    /* ... */
  };
  await enqueue(abortController.signal, callback4, callback4);
} catch (e) {
  // Here, we'll catch an `AbortError`.
}