{"id":18879712,"url":"https://github.com/loilo/branchy","last_synced_at":"2025-05-09T00:09:44.058Z","repository":{"id":30552095,"uuid":"125335626","full_name":"loilo/branchy","owner":"loilo","description":"🍃 Execute a Node.js function in a separate process","archived":false,"fork":false,"pushed_at":"2024-07-01T04:04:42.000Z","size":157,"stargazers_count":92,"open_issues_count":1,"forks_count":4,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-05-09T00:09:37.058Z","etag":null,"topics":["async","fork","javascript","nodejs"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/loilo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-03-15T08:29:16.000Z","updated_at":"2025-02-11T15:50:20.000Z","dependencies_parsed_at":"2024-06-19T06:13:43.055Z","dependency_job_id":"f8468312-0794-40c1-9100-8eccccd76fa2","html_url":"https://github.com/loilo/branchy","commit_stats":null,"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/loilo%2Fbranchy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/loilo%2Fbranchy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/loilo%2Fbranchy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/loilo%2Fbranchy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/loilo","download_url":"https://codeload.github.com/loilo/branchy/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253166521,"owners_count":21864482,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","fork","javascript","nodejs"],"created_at":"2024-11-08T06:38:56.246Z","updated_at":"2025-05-09T00:09:44.033Z","avatar_url":"https://github.com/loilo.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"![Branchy](https://cdn.jsdelivr.net/gh/Loilo/branchy/branchy.svg)\n\n[![Tests](https://badgen.net/github/checks/loilo/branchy/master)](https://github.com/loilo/branchy/actions)\n[![npm](https://badgen.net/npm/v/branchy)](https://www.npmjs.com/package/branchy)\n\nComfortly run Node.js functions in a separate process.\n\n```javascript\nconst forkedAsyncFunction = branchy(heavySyncFunction)\n```\n\n## Installation\n\n```bash\nnpm install --save branchy\n```\n\n## Basic Usage\n\nIt's super easy — pass your function to `branchy` and get an asynchronous, Promise-returning version of it:\n\n```javascript\nconst branchy = require('branchy')\n\n// Synchronous \"add\", returns number\nconst adder = (a, b) =\u003e a + b\n\n// Asynchronous \"add\" in a child process, returns Promise that resolves to number\nconst forkedAdder = branchy(adder)\n\n// Don't forget to wrap in async function\nawait forkedAdder(2, 3) // 5\n\n// This example just adds two numbers, please don't ever\n// put that work into an extra process in a real-world scenario\n```\n\nAlternatively, you could put the function in its own file and pass the file path to `branchy`:\n\n```javascript\n// add.js\nmodule.exports = (a, b) =\u003e a + b\n\n// index.js\nconst forkedAdder = branchy('./add')\n\nawait forkedAdder(2, 3) // 5\n```\n\n## Caveats\n\nThe technical procedures of `branchy` set some requirements for forked functions:\n\n- Parameters passed to a forked function will be serialized. That means, forked functions should only accept serializeable arguments. The same goes for their return values.\n- Forked functions are serialized before being run in a different process. Consequently, they have no access to the local variable scope that was available during their definition:\n\n  ```javascript\n  const branchy = require('branchy')\n\n  const foo = 42\n\n  branchy(() =\u003e {\n    return foo // ReferenceError: foo is not defined\n  })\n  ```\n\n- Although the outer scope is not available in a forked function, the `__filename` and `__dirname` variables are funnelled into the function with the values they have at the location where the function is passed to `branchy()`.\n\n  Also, the `require()` function works as expected – it resolves modules relative to the file where `branchy()` was called.\n\n  \u003e **Attention:** This means that you may _not_ pass functions to branchy which have been imported from another location. `__filename`, `__dirname` and `require()` won't work as expected.\n  \u003e To use functions from another file, pass their module specifier to branchy.\n  \u003e\n  \u003e ```javascript\n  \u003e // do this\n  \u003e const forkedFn = branchy('./fn')\n  \u003e\n  \u003e // not this\n  \u003e const forkedFn = branchy(require('./fn'))\n  \u003e ```\n\n## Advanced Usage\n\n### Concurrency Control\n\nTo avoid sharing work among too many processes, you may need to restrict how many child processes a function may create at the same time. For this use case, `branchy` offers some simple concurrency control.\n\nEnable concurrency control by passing an optional second argument to the `branchy()` function, specifying the `concurrent` option:\n\n```javascript\nconst fn = branchy('./computation-heavy-sync-task', { concurrent: 4 })\n```\n\nNo matter how often you call `fn()`, there will be no more than 4 processes of it running at the same time. Each additional call will be queued and executed as soon as a previous call finishes.\n\n\u003e **Note:** Passing a number as the `concurrent` option actually is a shorthand, you may pass an object to refine concurrency control:\n\u003e\n\u003e ```javascript\n\u003e { concurrent: 4 }\n\u003e\n\u003e // is equivalent to\n\u003e\n\u003e {\n\u003e   concurrent: {\n\u003e     threads: 4,\n\u003e     // other options\n\u003e   }\n\u003e }\n\u003e ```\n\n#### Automatically Choose Number of Concurrent Forks\n\nTo restrict concurrency to the number of available CPU cores, use `{ concurrent: 'auto' }`.\n\n#### Priority\n\nYou may define the priority of each call depending on its arguments:\n\n```javascript\nconst call = branchy(name =\u003e console.log('Call %s', name), {\n  concurrent: {\n    threads: 1, // Only one at a time for demoing purposes\n    priority: name =\u003e (name === 'Ghostbusters' ? 100 : 1)\n  }\n})\n\ncall('Alice')\ncall('Bob')\ncall('Ghostbusters')\n\n// \"Call Ghostbusters\", \"Call Alice\", \"Call Bob\"\n```\n\n- The `priority()` function will be passed the same arguments as the forked function itself.\n- Priority may be determined asynchronously (by returning a Promise).\n\n#### Call Order Strategy\n\nBy default, the queue starts processes in the order functions were called ([first-in, first-out](\u003chttps://en.wikipedia.org/wiki/FIFO_(computing_and_electronics)\u003e)). However you can make the queue handle the latest calls first (technically making it a [Stack](\u003chttps://en.wikipedia.org/wiki/Stack_(abstract_data_type)\u003e)) by setting the `strategy`:\n\n```javascript\n{\n  concurrent: {\n    strategy: 'stack'\n  }\n}\n```\n\n#### Concurrency Contexts\n\nWhile you now may control how many child processes a _single_ function creates, process limits are function-bound and not enforced across _different_ Branchy functions:\n\n```javascript\nconst inc = branchy(num =\u003e num + 1, { concurrent: 2 })\nconst dec = branchy(num =\u003e num - 1, { concurrent: 2 })\n\n// This opens 2 processes\ninc(1)\ninc(2)\ninc(3)\n\n// Another function, another context, so it opens another 2 processes\ndec(1)\ndec(2)\ndec(3)\n```\n\nThis is where concurrency contexts come in. A context encapsulates a concurrency configuration in a shareable `ConcurrencyContext` object with a single queue attached to it.\n\nCreate it like so:\n\n```javascript\nconst ctx = branchy.createContext({\n  threads: 2\n})\n```\n\nNow share the `ctx` across multiple forked functions, so the example above works as expected:\n\n```javascript\nconst inc = branchy(num =\u003e num + 1, { concurrent: ctx })\nconst dec = branchy(num =\u003e num - 1, { concurrent: ctx })\n\n// This opens 2 processes\ninc(1)\ninc(2)\ninc(3)\n\n// This correctly queues dec() calls after inc() calls\ndec(1)\ndec(2)\ndec(3)\n```\n\n#### Access the Queue\n\nA `ConcurrencyContext` is just an extended [Queue](https://www.npmjs.com/package/better-queue).\n\nIf you need more fine-grained control over currently running tasks, you may create a context for that:\n\n```javascript\nconst ctx = branchy.createContext({ threads: 4 })\n\n// For more information about the available API, see the `better-queue` docs\nctx.on('drain', () =\u003e {\n  console.log('All calls have been executed!')\n})\n\n// ...use the `ctx` context in branchy() calls\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Floilo%2Fbranchy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Floilo%2Fbranchy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Floilo%2Fbranchy/lists"}