Open-source project
miketalbot/js-coroutines avatar
miketalbot/js-coroutines

js-coroutines: yielding to the browser's idle gaps to keep the main thread free

60fps with Javascript Coroutines for idle processing and animation

552 stars7 forksJavaScriptNOASSERTION

At a glance

What is it?
js-coroutines is a JavaScript library that runs array, JSON and compression work inside generator functions that yield to the browser's idle time, so heavy processing stops blocking paints and animations. It fits browser and React Native apps that need to keep a frame budget, and it does not fit batch jobs that want a worker thread or a server with no idle callback.
Who is it for?
js-coroutines fits front end work that must stay inside a frame budget on modern browsers and React Native, where the same code is already written as generators or promises. It does not fit server side batch processing, since the README does not describe behaviour in a Node process where no idle callback exists, and the custom licence in LICENCE.txt should be read before it reaches a commercial product.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 11 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Sorting ten million items without freezing every animation

The README opens with a question rather than a feature list: when is the right time to sort a massive array on the main thread of a JavaScript app? Its answer is that the right time is in the gaps where the animation is not doing anything and the system is idle. The stated goal, phrased as a headline in the project, is getting 60fps while sorting an array of 10 million items with js-coroutines. The audience is front end developers whose work is long enough to cause visible jank but who cannot move it elsewhere, and the argument against the obvious alternatives is stated plainly. Transferring work to a worker thread is said to hit the main thread for serialization and stutter everything, and doing the work inline makes the user see all animations and effects jank. The library is aimed squarely at that middle ground.

How run() and requestIdleCallback split the work

The mechanism is small enough to describe in a sentence: js-coroutines uses generator functions and requestIdleCallback to let you split up your work with minimal effort. A generator yields, and the library resumes it in a later idle gap, so control returns to the browser between chunks. The README describes your own generator functions as doing anything you like and yielding to check if there is time remaining this frame. The key difference from a hand written idle loop is that the yield points are placed where you choose and the library decides when to continue. Built on that model are array primitives that come ready with the most useful functions, and a second mode where the same work is exposed as promise returning helpers for code that does not want to be written as generators.

Installing js-coroutines and running a first coroutine

Installation is a single npm command, and the package is published under the name js-coroutines:

sh
npm install --save js-coroutines

A first coroutine builds an array, yields periodically to let the browser paint, then hands the array to the library's own coroutine sort and stringify helpers. The imports come from the package root, and the pattern is to wrap the generator in run and await its result:

js
import {run, sort, stringify} from 'js-coroutines'

...

let json = await run(function*() {
  const results = [];
  for(let i = 0; i < 10000000; i++) {
    results.push(Math.random() * 10000);
    //Check how much time left every 100 entries
    if(i % 100 === 0) yield;
  }
  //Pass to a coroutine sort function
  yield* sort(results, value=>value)
  return yield* stringify(results);
})

Two details in that example carry the whole idea. The yield happens every 100 entries rather than on every iteration, so the loop stays cheap, and the sort and stringify helpers are delegated with yield* so they can yield internally as well instead of blocking.

The Async helpers for code that does not want generators

For many applications the generator form is more machinery than needed, and the README offers a second style: the Async helper functions inside an ordinary async routine, described as less capable but freeing you from working out where to yield. The helper names carry an Async suffix and return promises, so they slot into an existing promise chain or an await. The example fetches a response and then parses the body with parseAsync rather than the blocking .json() method, then maps rows with mapAsync:

js
import { parseAsync, mapAsync } from "js-coroutines";

async function process(url) {
  const response = await fetch("someurl");
  //Use the coroutine version of parse, rather than blocking
  //the main thread permanently by using .json()
  const result = await parseAsync(await response.text());
  //Imagining the result is some database rows, map out the
  //desired response without blocking the main thread for paints
  const values = await mapAsync(result, (row) => ({
    item: row.time,
    value: row.quantity * row.unitPrice,
  }));
  return values;
}

The same style covers sorts, finds, filters and JSON parsing, and a longer example in the README chains concatAsync, stringifyAsync and mapAsync in a loop, which is the cheapest way to see the shape of the promise based API. The library also offers pure functional pipelines built from pipe, tap, branch, repeat and call, with a .with modifier on helpers such as mapAsync, so a pipeline can mix library stages with your own generator function.

The update method for per-frame animation work

Beyond idle processing, the README describes a second use: animating and controlling complex states. The library provides an update method that runs every frame in high priority, and the project points at a stateful animation example and a game built with js-coroutines. This is the interesting tension in the design. Idle time is exactly what the first mode exploits, and the second mode deliberately takes time away from the browser on every frame, running at high priority so that animation state is not delayed by other work. The two modes are aimed at different moments: one fills gaps that would otherwise be wasted, the other claims a slice of every frame. The documentation does not describe how the update callback interacts with coroutines that are also running, which is worth checking in a real app.

JSON and compression helpers built on lz-string

The commonly required asynchronous operations are covered by four pairs of functions. The star prefixed forms, *stringify and *parse, manipulate JSON inside an idle coroutine that will not block the main thread, while stringifyAsync and parseAsync do the same work anywhere you can take a promise or await a response. Compression works the same way, with *compress and *decompress producing storable or transmittable strings, and compressAsync and decompressAsync offering the promise based equivalents. Underneath, the README states that js-coroutines uses lz-string for compression and links to that project for documentation. That is a real dependency with its own trade-offs: compressed output is smaller but the format is not a general purpose codec, and the README does not state how the compressed data is versioned or whether it interoperates with lz-string used elsewhere in your stack.

A custom licence, modern browser targets, and a recent last push

The licensing is worth attention. The project is not under a licence that GitHub can classify, and the terms live in a file named LICENCE.txt at the repository root, which is included in the published files along with dist and the README. Nothing in the README summarises those terms, so read the file directly before using the library in a product, and do not assume a permissive licence because the code is public. On the technical side, the README states that modern browsers and React Native are supported and that IE11 is no longer targeted, and the package browserslist field matches that with defaults, not IE 11, and maintained node versions. The package version is 3.0.0, the main entry point is dist/index.js, and a polyfill is marked as a side effect so bundlers keep it. The repository has no GitHub releases, and the last push was on 2026-10-12, so the version number in package.json is the only handle on what you have installed.

Editorial conclusion

js-coroutines fits front end work that must stay inside a frame budget on modern browsers and React Native, where the same code is already written as generators or promises. It does not fit server side batch processing, since the README does not describe behaviour in a Node process where no idle callback exists, and the custom licence in LICENCE.txt should be read before it reaches a commercial product. Verify first that your target browsers expose requestIdleCallback, then measure your own interaction latency with a real interaction, and check the LICENCE.txt terms yourself before shipping it.

Frequently asked questions

What is js-coroutines and what problem does it solve?

js-coroutines is a JavaScript library that uses generator functions and requestIdleCallback to split heavy work into pieces that run in the browser's idle gaps. The aim is to keep animations and effects smooth while sorting, parsing or compressing large amounts of data on the main thread.

How do I install js-coroutines and use it with async functions?

Install it with npm install --save js-coroutines, then import the Async helpers such as parseAsync or mapAsync and await them inside an ordinary async function. The README describes this as the less capable but easier option when you do not want to write generator functions.

Does js-coroutines work in React Native and in older browsers?

The README states that js-coroutines supports modern browsers and React Native, and that IE11 is no longer targeted. The browserslist field in package.json lists defaults, not IE 11, and maintained node versions.

What licence is js-coroutines released under?

The licence is a custom one that GitHub cannot classify, and the terms are in the LICENCE.txt file at the repository root, which is published with the package. The README does not summarise those terms.

Official sources

  1. Issues
  2. miketalbot/js-coroutines on GitHub
  3. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/miketalbot-js-coroutines.svg)](https://hysenlabs.com/projects/miketalbot-js-coroutines)

Was this useful?