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

dpa

v2.0.0

Published

Resolves promises concurrently with deterministic rejection order. Somewhere between Promise.all and Promise.allSettled.

Downloads

71

Readme

Features

  • Deterministic: always rejects with the same promise for a list of promises where some reject regardless of which one rejects first
  • Performant: always resolves or rejects as fast as or faster than Promise.allSettled for the same input
  • Familiar: same API as Promise.all including iterables support
  • Tiny: ~130 bytes minzipped!

Install

$ npm i dpa

Huh? What? Why?

Deterministic promise all. I promise (pun intended!) it's not as esoteric as it sounds!

Suppose you have some asynchronous authorization functions, each resolving to undefined or rejecting, that you'd like to run on all requests to an endpoint. You care about performance! So you run the authorization checks concurrently using Promise.all and display an error to the user on rejection.

Using Remix it might look something like this:

import { useCatch } from 'remix'

export const loader = async ({ request }) => {
  await Promise.all([
    checkThing1(request),
    checkThing2(request),
    checkThing3(request),
  ])

  // Do authorized stuff...
}

// Rendered when an error response is thrown in `loader`
export const CatchBoundary = () => {
  const caught = useCatch()

  // Return some JSX...
}

The problem

Everything seems to work great, but what if the promises returned by checkThing1 and checkThing2 both reject? What does the user see? The answer is it depends on which one rejects first!

That's right. Your error page is nondeterministic. The user can visit the same URL with the same authorization state and receive a different page purely based on how quickly each authorization check completes.

Another problematic case in a framework like Remix is redirects. In Remix you can redirect by throwing redirect responses. If you use Promise.all to concurrently run a bunch of functions that may throw redirects, then your redirects are nondeterministic too.

The solution

You could use Promise.allSettled to wait for all the promises to either resolve or reject, then if any reject you could reject with the first rejected promise in your list of promises. This way you reject with the same promise regardless of which one rejects first (time-wise). This is what Remix was doing to keep concurrent loader execution deterministic (and maybe still does, but I couldn't figure where that's done at the latest commit).

But can we do better? What if the first promise in your list is the first promise to reject? We could reject right away and still be deterministic! But with Promise.allSettled we're stuck waiting for every promise to resolve in all cases...

More generally, if a promise in your list rejects and every promise before it in the list resolved, then we can immediately reject with that promise. That's what dpa does. It's deterministic while being strictly as fast as or faster than Promise.allSettled!

Usage

import dpa from 'dpa'

const delay = ms => new Promise(resolve => setTimeout(resolve, ms))
const test = async fn => {
  const start = Date.now()
  try {
    console.log(await fn())
  } catch (error) {
    console.log(`${error} thrown`)
  } finally {
    const elapsed = Date.now() - start
    console.log(`${Math.trunc(elapsed / 1000)}s elapsed`)
  }
  console.log()
}

await test(() =>
  dpa([
    delay(1000).then(() => 1),
    delay(4000).then(() => 2),
    delay(5000).then(() => 3),
  ]),
)
// => [1, 2, 3]
// => 5s elapsed

await test(() =>
  dpa([
    delay(1000).then(() => 1),
    delay(4000).then(() => {
      throw 2
    }),
    delay(6000).then(() => {
      throw 3
    }),
  ]),
)
// => 2 thrown
// => 4s elapsed

await test(() =>
  dpa([
    dpa([
      delay(1000).then(() => 1),
      delay(6000).then(() => {
        throw 2
      }),
      delay(2000).then(() => {
        throw 3
      }),
    ]),
  ]),
)
// => 2 thrown
// => 6s elapsed

Contributing

Stars are always welcome!

For bugs and feature requests, please create an issue.

For pull requests, please read the contributing guidelines.

License

Apache License 2.0

This is not an official Google product.