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 🙏

© 2025 – Pkg Stats / Ryan Hefner

goroutines

v2.0.0

Published

Inspired by Go's Goroutines, this package adds an easy ability to trivially multithread (and potentially multiprocess) your code (supports NodeJS and Bun)

Readme

Goroutines.js

Inspired by Go's Goroutines, this package adds an easy ability to trivially multithread (and potentially multiprocess) your code (supports NodeJS and Bun)

Note about concurrency

Concurrency is a difficult thing to get right that should only be applied in circumstances where it's warranted. It's not recommended to use it for simple operations due to the bootstrap time of using workers. Be sure to benchmark your code using concurrency vs single-threaded.

Be sure to also avoid fork-bombing. Most runtimes are optimized for recursion so expensive recursive functions should be run in their entirety on a thread, rather than running each recursion on its own thread (which can lead to a segfault)

Installation

npm install goroutines

Getting Started

Take a costly function,

function fib (n: number): number {
  if (n <= 1) return n
  return fib(n - 1) + fib(n - 2)
}

const result = fib(40)
console.log(resut)

This is a heavy function that blocks the event loop. It can be made asynchronous with the async, but still runs on the same thread (JS is single-threaded) which can be taxing.

Instead, wrap it into a goroutine

import { go } from 'goroutines'

const result = await go(fib)(40)
console.log(result)

The function will now run on a separate thread, leaving your main process unblocked

[!IMPORTANT] Technically, we're still blocking the main thread in this example with a top-level await. Make sure to read into how promises work

Data Streaming

Another useful capability of Goroutines is their ability to stream data. You can accomplish something similar using generator functions.

import { go } from '../src'

const shared = new SharedArrayBuffer(1_000_000)
const array = new Uint8Array(shared)
crypto.getRandomValues(array)

function* getEven(buffer: SharedArrayBuffer) {
  const data = new Uint8Array(buffer)
  for (let i = 0; i < data.length; ++i) {
    const value = Atomics.load(data, i)
    if (!(value % 2)) yield value
  }
}

const iter = go(getEven)(shared)
let ret
do {
  ret = await iter.next()
  if (ret.done) break

  console.log(ret.value)
} while (!ret.done)

[!NOTE] If this function seems confusing, yield functions as a return without actually ending the function and iter.next() receives that value.

This will function exactly like an async generator.

[!TIP] A regular array can be serialized just as well but results in cloning which is costly.

See: Supported data types

See: SharedArrayBuffer

You can iterate through it using the for await syntax

for await (const chunk of go(getEven)(shared)) { ... }

You can pass data back to the thread

// Dramatized example for demonstration. DO NOT DO THIS
function* pwValidator (password: string) {
  while (true) {
    const attempt = yield 'Enter a password'
    if (attempt === password) {
      yield 'Correct!'
      break
    } else yield 'Incorrect!'
  }
}

const guesser = go(pwValidator)('super secret password')
await guesser.next()

const guess = 'wrong password'
const result = await guesser.next(guess)
console.log(result.value)

[!TIP] You can read more into the specifics of how generator functions pass values between yields here

Context and Imports

If your function uses an out-of-scope variable, you can pass it to your goroutine via the context parameter

import suspectdata from './suspects.json' with { type: 'json' }

const CRIME_SCENE = 'The Museum'
const CRIME_DATE = '2025-07-17T20:03:11.952Z'

interface Suspect {
  name: string
  locations: Partial<Record<string, string>>
}

const suspects: Suspect[] = suspectdata

function findPerp () {
  return suspects.find((s) => s.locations[CRIME_DATE] === CRIME_SCENE)?.name
}

const suspect = await go(findPerp, {
  CRIME_SCENE,
  CRIME_DATE,
  suspects
})()
console.log(suspect)

Firstly, notice an optimization we can do. Instead of importing the data here and passing it to the worker, we can import it directly into the worker.

Secondly, simply logging the suspect isn't enough. We must show them for who they are in all of their evil glory. To accomplish this, we'll style the text red using styleText from 'util'

const CRIME_SCENE = 'The Museum'
const CRIME_DATE = '2025-07-17T20:03:11.952Z'

interface Suspect {
  name: string
  locations: Partial<Record<string, string>>
}

function findPerp () {
  // @ts-expect-error
  declare const suspects: Suspect[]
  // @ts-expect-error
  declare const styleText: typeof import('util').styleText

  const name = suspects.find((s) => s.locations[CRIME_DATE] === CRIME_SCENE)?.name
  return name && styleText('red', name)
}

const suspect = await go(findPerp, {
  CRIME_SCENE,
  CRIME_DATE
}, {
  './suspects.json': ['default as suspects'],
  // Due to the behavior of the worker api, relative modules are usually resolved from the CWD, not the file directory. Bun exposes an API for module location resolution:
  // [Bun.resolveSync('./suspects.json', import.meta.dirname)]: ['default as suspects'],
  util: ['styleText']
})()
console.log(suspect)

[!CAUTION] Try to avoid passing large amounts of data to workers via context, function call parameters, or generator passing If the dataset already exists in a file and isn't required in the main thread, import it in the goroutine.