{"id":20904666,"url":"https://github.com/sage/f-promise","last_synced_at":"2025-08-20T18:58:06.074Z","repository":{"id":13793327,"uuid":"74968555","full_name":"Sage/f-promise","owner":"Sage","description":"Promise-oriented coroutines for node.js","archived":false,"fork":false,"pushed_at":"2022-03-04T13:06:56.000Z","size":114,"stargazers_count":22,"open_issues_count":2,"forks_count":4,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-08-09T05:58:53.558Z","etag":null,"topics":["async","coroutine","fibers","promise"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","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/Sage.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}},"created_at":"2016-11-28T11:55:50.000Z","updated_at":"2024-05-19T21:15:25.000Z","dependencies_parsed_at":"2022-08-07T07:15:43.322Z","dependency_job_id":null,"html_url":"https://github.com/Sage/f-promise","commit_stats":null,"previous_names":[],"tags_count":38,"template":false,"template_full_name":null,"purl":"pkg:github/Sage/f-promise","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sage%2Ff-promise","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sage%2Ff-promise/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sage%2Ff-promise/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sage%2Ff-promise/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Sage","download_url":"https://codeload.github.com/Sage/f-promise/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sage%2Ff-promise/sbom","scorecard":{"id":124956,"data":{"date":"2025-08-11","repo":{"name":"github.com/Sage/f-promise","commit":"822fb2165b8b13ce6c86a7c978551ba569a0dd9f"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.9,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":0,"reason":"Found 1/14 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":1,"reason":"9 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-4q6p-r6v2-jvc5","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-vh95-rmgr-6w4m","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-hj48-42vr-x3v9","Warn: Project is vulnerable to: GHSA-g6ww-v8xp-vmwg","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-29xr-v42j-r956"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 26 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-16T03:34:14.928Z","repository_id":13793327,"created_at":"2025-08-16T03:34:14.929Z","updated_at":"2025-08-16T03:34:14.929Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271369161,"owners_count":24747792,"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","status":"online","status_checked_at":"2025-08-20T02:00:09.606Z","response_time":69,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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","coroutine","fibers","promise"],"created_at":"2024-11-18T13:18:26.495Z","updated_at":"2025-08-20T18:58:06.041Z","avatar_url":"https://github.com/Sage.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# f-promise\n\nPromise-oriented coroutines for node.js.\n\n```sh\nnpm install f-promise\n```\n\n## API\n\nThe `f-promise` API consists in 2 calls: `wait` and `run`.\n\n* `result = wait(promise)`:  waits on a promise and returns its result (or throws if the promise is rejected).\n* `promise = run(fn)`: runs a function as a coroutine and returns a promise for the function's result.\n\nConstraint: `wait` may only be called from a coroutine (a function which is executed by `run`, directly or indirectly, through one of its callers).\n\n## Simple example\n\n```js\nimport { wait, run } from 'f-promise';\nimport * as fs from 'mz/fs';\nimport { join } from 'path';\n\nfunction diskUsage(dir) {\n    return wait(fs.readdir(dir)).reduce((size, name) =\u003e {\n        const sub = join(dir, name);\n        const stat = wait(fs.stat(sub));\n        if (stat.isDirectory()) return size + diskUsage(sub);\n        else if (stat.isFile()) return size + stat.size;\n        else return size;\n    }, 0);\n}\n\nfunction printDiskUsage(dir) {\n    console.log(`${dir}: ${diskUsage(dir)}`);\n}\n\nrun(() =\u003e printDiskUsage(process.cwd()))\n    .then(() =\u003e {}, err =\u003e { console.error(err); });\n```\n\nNote: this is not a very efficient implementation because the logic is completely\nserialized.\n\n## Why f-promise?\n\nTo understand the benefits of `f-promise`, let us compare the example above with the ES7 `async/await` equivalent:\n\n```js\nimport * as fs from 'mz/fs';\nimport { join } from 'path';\n\nasync function diskUsage(dir) {\n    var size = 0;\n    for (var name of await fs.readdir(dir)) {\n        const sub = join(dir, name);\n        const stat = await fs.stat(sub);\n        if (stat.isDirectory()) size += await diskUsage(sub);\n        else if (stat.isFile()) size += stat.size;\n    }\n    return size;\n}\n\nasync function printDiskUsage(dir) {\n    console.log(`${dir}: ${await diskUsage(dir)}`);\n}\n\nprintDiskUsage(process.cwd())\n    .then(() =\u003e {}, err =\u003e { console.error(err); });\n```\n\nTwo observations:\n\n* Async is contagious: `printDiskUsage` must be marked as `async` \nbecause it needs to `await` on `diskUsage`.\nThis is not dramatic in this simple example but in a large code base this translates\ninto a proliferation of `async/await` keywords throughout the code.\n* ES7 async/await does not play well with array methods (`forEach`, `map`, `reduce`, ...) \nbecause you cannot use `await` inside the callbacks of these methods. \nYou have to write the loop differently, with `for ... of ...` or `Promise.all`.\n\n`f-promise` solves these problems:\n\n* Functions that _wait_ on async operations are not marked with `async`; \nthey are _normal_ JavaScript functions. `async/await` keywords don't invade the code.\n* `wait` plays well with array methods, and with other APIs that expect _synchronous_ callbacks.\n\nCoroutines have other advantages, like providing complete meaningful stacktraces without any overhead.\n\n## TypeScript support\n\nTypeScript is fully supported.\n\n## Callbacks support\n\nYou can also use `f-promise` with callback APIs. \nSo you don't absolutely need wrappers like `mz/fs`, you can directly call node's `fs` API:\n\n```javascript\nimport { wait } from 'f-promise';\n\n// promise style\nimport * as mzfs from 'mz/fs';\nconst readdir = path =\u003e wait(mzfs.readdir(path));\n\n// callback style\nimport * as fs from 'fs';\nconst readdir = path =\u003e wait(cb =\u003e fs.readdir(path, cb));\n````\n\n## Control Flow utilities\n\nThese goodies solve some common problems and offer an easy upgrade path from streamline.js (which bundled a similar API).\n \n### funnel\n\n* `fun = fpromise.funnel(max)`  \n  limits the number of concurrent executions of a given code block.\n\nThe `funnel` function is typically used with the following pattern:\n\n``` javascript\nimport { funnel } from 'f-promise';\n\n// somewhere\nvar myFunnel = funnel(10); // create a funnel that only allows 10 concurrent executions.\n\n// elsewhere\nmyFunnel(() =\u003e { /* code with at most 10 concurrent executions */ });\n```\n\nThe `funnel` function can also be used to implement critical sections. Just set funnel's `max` parameter to 1.\n\nIf `max` is set to 0, a default number of parallel executions is allowed. \nThis default number can be read and set via `funnel.defaultSize`.  \nIf `max` is negative, the funnel does not limit the level of parallelism.\n\nThe funnel can be closed with `fun.close()`.  \nWhen a funnel is closed, the operations that are still in the funnel will continue but their callbacks\nwon't be called, and no other operation will enter the funnel.\n\n### handshake and queue\n\n* `hs = fpromise.handshake()`  \n  allocates a simple semaphore that can be used to do simple handshakes between two tasks.  \n  The returned handshake object has two methods:  \n  `hs.wait()`: waits until `hs` is notified.  \n  `hs.notify()`: notifies `hs` (without waiting for an acknowledgement)\n  Note: `wait` calls are not queued. An exception is thrown if wait is called while another `wait` is pending.\n* `q = fpromise.queue(options)`  \n  allocates a queue which may be used to send data asynchronously between two tasks.  \n  The `max` option can be set to control the maximum queue length.  \n  When `max` has been reached `q.put(data)` discards data and returns false.\n  The returned queue has the following methods:  \n  `data = q.read()`: dequeues an item from the queue. Waits if no element is available.  \n  `q.write(data)`:  queues an item. Waits if the queue is full.  \n  `ok = q.put(data)`: queues an item synchronously. Returns true if the queue accepted it, false otherwise.  \n  `q.end()`: ends the queue. This is the synchronous equivalent of `q.write(undefined)`  \n  `data = q.peek()`: returns the first item, without dequeuing it. Returns `undefined` if the queue is empty.  \n  `array = q.contents()`: returns a copy of the queue's contents.  \n  `q.adjust(fn[, thisObj])`: adjusts the contents of the queue by calling `newContents = fn(oldContents)`.  \n  `q.length`: number of items currently in the queue.  \n \n### CLS (Continuation Local Storage)\n\n* `cx = fpromise.context()`  \n  returns the current context.\n\n* `fn = fpromise.withContext(fn, cx)`  \n  wraps a function so that it executes with context `cx` (or a wrapper around current context if `cx` is falsy).\n  The previous context will be restored when the function returns (or throws).  \n  returns the wrapped function.\n\n### Miscellaneous\n\n* `results = fpromise.map(collection, fn)`  \n  creates as many coroutines with `fn` as items in `collection` and wait for them to finish to return result array.\n \n* `fpromise.sleep(ms)`  \n  suspends current coroutine for `ms` milliseconds.\n \n* `ok = fpromise.canWait()`  \n  returns whether `wait` calls are allowed (whether we are called from a `run`).\n    \n* `wrapped = fpromise.eventHandler(handler)`  \n  wraps `handler` so that it can call `wait`.  \n  the wrapped handler will execute on the current fiber if canWait() is true.\n  otherwise it will be `run` on a new fiber (without waiting for its completion)  \n  \n### Error stack traces\n\nThree policies available for error stack trace handling:\n* `fast`: stack traces are not changed. Call history might be difficult to read; cost less.\n* `whole`: stack traces due to async tasks errors in `wait()` are concatenate with the current coroutine stack.\n  This allow to have a complete history call (including f-promise traces).\n* default: stack traces are like `whole` policy, but clean up to remove f-promise noise.\n\nThe policy can be set with `FPROMISE_STACK_TRACES` environment variable.\nAny value other than `fast` and `whole` are consider as default policy. \n\n## Related projects\n\n* [f-streams](https://github.com/Sage/f-streams)\n* [f-express](https://github.com/Sage/f-express)\n* [f-mocha](https://github.com/Sage/f-mocha)\n\n## License\n\nMIT.\n\n## Credits\n\n`f-promise` is just a thin layer. All the hard work is done by the [`fibers`](https://github.com/laverdet/node-fibers) library.\n\n## Gotchas\n\nThe absence of `async/await` markers in code that calls asynchronous APIs is unusual in JavaScript (and considered harmful by some).\nBut this is the norm in other languages. Basically `f-promise` enables _goroutines_ in JavaScript.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsage%2Ff-promise","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsage%2Ff-promise","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsage%2Ff-promise/lists"}