{"id":13658353,"url":"https://github.com/artdecocode/erotic","last_synced_at":"2025-04-24T08:32:03.560Z","repository":{"id":57226687,"uuid":"107045259","full_name":"artdecocode/erotic","owner":"artdecocode","description":"Anchor asynchronous errors to line where you'd want them to throw.","archived":false,"fork":false,"pushed_at":"2019-04-10T23:55:23.000Z","size":532,"stargazers_count":8,"open_issues_count":1,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-10-13T20:07:42.511Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://artdeco.bz/erotic","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/artdecocode.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2017-10-15T20:07:41.000Z","updated_at":"2024-02-04T16:47:19.000Z","dependencies_parsed_at":"2022-08-22T12:21:05.464Z","dependency_job_id":null,"html_url":"https://github.com/artdecocode/erotic","commit_stats":null,"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/artdecocode%2Ferotic","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/artdecocode%2Ferotic/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/artdecocode%2Ferotic/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/artdecocode%2Ferotic/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/artdecocode","download_url":"https://codeload.github.com/artdecocode/erotic/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223947344,"owners_count":17230014,"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":[],"created_at":"2024-08-02T05:00:58.956Z","updated_at":"2024-11-10T11:31:44.894Z","avatar_url":"https://github.com/artdecocode.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"\u003ca href=\"https://artdeco.bz/erotic\"\u003e\u003cimg align=\"right\" src=\"./logo.jpg\" width=\"225\" alt=\"Erotic\" /\u003e\n\n# erotic\u003c/a\u003e\n\n[![npm version](https://badge.fury.io/js/erotic.svg)](https://npmjs.org/package/erotic)\n\n`erotic` is a Node.js package to capture asynchronous errors as if they occurred synchronously. It aims at keeping the error stack readable and developer-friendly. Furthermore, it can make errors appear as if they happened outside of the function at the point of call.\n\n```\nyarn add -E erotic\n```\n\n## Table of Contents\n\n- [Table of Contents](#table-of-contents)\n- [Quick Examples](#quick-examples)\n  * [Node.js: Reading A File](#nodejs-reading-a-file)\n  * [`erotic`: Standard Mode](#erotic-standard-mode)\n  * [`erotic`: Transparent Mode](#erotic-transparent-mode)\n- [API](#api)\n  * [`erotic(transparent?: boolean): Callback`](#erotictransparent-boolean-callback)\n  * [`Callback(messageOrError: string|Error): Error`](#callbackmessageorerror-stringerror-error)\n    * [Strict Mode](#strict-mode)\n  * [Transparent Mode](#transparent-mode)\n    * [Use Case: Assertion Library](#use-case-assertion-library)\n- [Copyright](#copyright)\n\n## Quick Examples\n\nThe following examples show the benefits of using `erotic`.\n\n### Node.js: Reading A File\n\nWhen reading a file with Node's `readFile` method from the `fs` package, the function will be rejected without any error stack, which can make tracing errors harder in the application.\n\n```js\nimport { readFile } from 'fs'\n\nconst read = async (path) =\u003e {\n  await new Promise((resolve, reject) =\u003e {\n    readFile(path, (err, data) =\u003e {\n      if (err) return reject(err)\n      return resolve(data)\n    })\n  })\n}\n\n(async () =\u003e {\n  const path = 'non-existent-file.txt'\n  try {\n    await read(path)\n  } catch ({ stack }) {\n    console.log(stack)\n  }\n})()\n```\n\n```\nError: ENOENT: no such file or directory, open 'non-existent-file.txt'\n```\n\n### `erotic`: Standard Mode\n\n`erotic` solves the problem described in the _Node.js_ example above by remembering the error stack at the point of where the function was called.\n\n```js\nimport { readFile } from 'fs'\nimport erotic from 'erotic'\n\nconst read = async (path) =\u003e {\n  const er = erotic() // stack has the anchor point\n\n  await new Promise((resolve, reject) =\u003e {\n    readFile(path, (err, data) =\u003e {\n      if (err) {\n        const e = er(err) // stack also includes this line\n        return reject(e)\n      }\n      return resolve(data)\n    })\n  })\n}\n\n(async function example() {\n  const path = 'non-existent-file.txt'\n  try {\n    await read(path)\n  } catch ({ stack }) {\n    console.log(stack)\n  }\n})()\n```\n\n```\nError: ENOENT: no such file or directory, open 'non-existent-file.txt'\n    at ReadFileContext.readFile [as callback] (/Users/zavr/adc/erotic/example/read-file.js:10:19)\n    at read (/Users/zavr/adc/erotic/example/read-file.js:5:14)\n    at example (/Users/zavr/adc/erotic/example/read-file.js:21:11)\n    at Object.\u003canonymous\u003e (/Users/zavr/adc/erotic/example/read-file.js:25:3)\n```\n\n### `erotic`: Transparent Mode\n\nA transparent mode can be used when it's needed to completely proxy the call to a function, and hide all underlying error stack, making the error appear to happen at the point where the throwing function was called.\n\n```js\nimport { readFile } from 'fs'\nimport erotic from 'erotic'\n\nconst read = async (path) =\u003e {\n  const er = erotic(true)\n\n  await new Promise((resolve, reject) =\u003e {\n    readFile(path, (err, data) =\u003e {\n      if (err) {\n        const e = er(err)\n        return reject(e)\n      }\n      return resolve(data)\n    })\n  })\n}\n\n(async function example() {\n  const path = 'non-existent-file.txt'\n  try {\n    await read(path) // error appears to be thrown here\n  } catch ({ stack }) {\n    console.log(stack)\n  }\n})()\n```\n\n```\nError: ENOENT: no such file or directory, open 'non-existent-file.txt'\n    at example (/Users/zavr/adc/erotic/example/transparent.js:21:11)\n    at Object.\u003canonymous\u003e (/Users/zavr/adc/erotic/example/transparent.js:25:3)\n```\n\n## API\n\nThe package exports the default `erotic` function.\n\n```js\nimport erotic from 'erotic'\n```\n\n\u003cp align=\"center\"\u003e\u003ca href=\"#table-of-contents\"\u003e\u003cimg src=\".documentary/section-breaks/0.svg?sanitize=true\" width=\"25\"\u003e\u003c/a\u003e\u003c/p\u003e\n\n### `erotic(`\u003cbr/\u003e\u0026nbsp;\u0026nbsp;`transparent?: boolean,`\u003cbr/\u003e`): Callback`\n\nCreates a callback which should be used before throwing any errors to make their stack appear at the point of creation of the callback. The `transparent` option can be used to hide this line also and make the function's errors' stacks start at the caller's line.\n\nWhen creating a library which runs some asynchronous code, the callback should be created when entering the function's body, and called at some point in future to update an error's stack before throwing.\n\n### `Callback(`\u003cbr/\u003e\u0026nbsp;\u0026nbsp;`messageOrError: string|Error,`\u003cbr/\u003e`): Error`\n\nReturns an error with the remembered stack to be thrown. In the example below, a function is created which can throw at some point in future, but its stack trace will begin inside the call to it and not at Node's `setTimeout` internals.\n\nWhen the callback is called with an error, the error's stack is overridden with a new stack, but all other properties are preserved, and the error is strictly equal to the one passed.\n\n```js\nimport erotic from 'erotic'\n\nasync function wait() {\n  const cb = erotic()\n  await new Promise((_, reject) =\u003e {\n    setTimeout(() =\u003e {\n      const err = new Error('Promise timeout error.')\n      err.code = 'ETIMEOUT'\n      const error = cb(err)\n      reject(error)\n    }, 10)\n  })\n}\n\n(async function example() {\n  try {\n    await wait()\n  } catch ({ stack, code }) {\n    console.log(stack)\n    console.log(code)\n  }\n})()\n```\n\n```\nError: Promise timeout error.\n    at Timeout.setTimeout [as _onTimeout] (/Users/zavr/adc/erotic/example/set-timeout.js:9:21)\n    at wait (/Users/zavr/adc/erotic/example/set-timeout.js:4:14)\n    at example (/Users/zavr/adc/erotic/example/set-timeout.js:17:11)\n    at Object.\u003canonymous\u003e (/Users/zavr/adc/erotic/example/set-timeout.js:22:3)\nETIMEOUT\n```\n\nWhen a string is passed, an error object is created with the message internally.\n\n```js\nimport erotic from 'erotic'\n\nasync function wait() {\n  const cb = erotic()\n  await new Promise((_, reject) =\u003e {\n    setTimeout(() =\u003e {\n      const error = cb('Promise timeout error.')\n      reject(error)\n    }, 10)\n  })\n}\n\n(async function example() {\n  try {\n    await wait()\n  } catch ({ stack }) {\n    console.log(stack)\n  }\n})()\n```\n\n```\nError: Promise timeout error.\n    at Timeout.setTimeout [as _onTimeout] (/Users/zavr/adc/erotic/example/set-timeout-string.js:7:21)\n    at wait (/Users/zavr/adc/erotic/example/set-timeout-string.js:4:14)\n    at example (/Users/zavr/adc/erotic/example/set-timeout-string.js:15:11)\n    at Object.\u003canonymous\u003e (/Users/zavr/adc/erotic/example/set-timeout-string.js:19:3)\n```\n\n#### Strict Mode\n\nThe `erotic` also works fine even in the `strict` mode.\n\n```js\n'use strict'\n\nimport erotic from 'erotic'\n\nconst wait = async () =\u003e {\n  const cb = erotic()\n  await new Promise((_, reject) =\u003e {\n    setTimeout(() =\u003e {\n      const err = new Error('Promise timeout error.')\n      const error = cb(err)\n      reject(error)\n    }, 10)\n  })\n}\n\n(async function example() {\n  try {\n    await wait()\n  } catch ({ stack }) {\n    console.log(stack)\n  }\n})()\n```\n\n```\nError: Promise timeout error.\n    at Timeout.setTimeout [as _onTimeout] (/Users/zavr/adc/erotic/example/set-timeout-strict.js:10:21)\n    at wait (/Users/zavr/adc/erotic/example/set-timeout-strict.js:6:14)\n    at example (/Users/zavr/adc/erotic/example/set-timeout-strict.js:18:11)\n    at Object.\u003canonymous\u003e (/Users/zavr/adc/erotic/example/set-timeout-strict.js:22:3)\n```\n\n### Transparent Mode\n\nIn the transparent mode, the stack will start where the function was called and not show any of its internals.\n\n```js\nimport erotic from 'erotic'\n\nasync function wait() {\n  const cb = erotic(true)\n  await new Promise((_, reject) =\u003e {\n    setTimeout(() =\u003e {\n      const error = cb('Promise timeout error.')\n      reject(error)\n    }, 10)\n  })\n}\n\n(async function example() {\n  try {\n    await wait()\n  } catch ({ stack }) {\n    console.log(stack)\n  }\n})()\n```\n\n```\nError: Promise timeout error.\n    at example (/Users/zavr/adc/erotic/example/set-timeout-transparent.js:15:11)\n    at Object.\u003canonymous\u003e (/Users/zavr/adc/erotic/example/set-timeout-transparent.js:19:3)\n```\n\n#### Use Case: Assertion Library\n\nFor example, when implementing an assertion library, uses will not want to see the details about how the error was created internally. They will only want to know that an error happened at a particular line in their test.\nThere will also be an internal _Node.js_ error stack, such as lines with `Module._compile` which are not useful.\n\nWithout `erotic`, the full error stack will be exposed:\n\n```js\nconst assertEqual = (actual, expected) =\u003e {\n  if (actual != expected) {\n    throw new Error(`${actual} != ${expected}`)\n  }\n}\n\n(function test() {\n  try {\n    assertEqual('hello', 'world')\n  } catch ({ stack }) {\n    console.log(stack)\n  }\n})()\n```\n\n```\nError: hello != world\n    at assertEqual (/Users/zavr/adc/erotic/example/assert-node.js:3:11)\n    at test (/Users/zavr/adc/erotic/example/assert-node.js:9:5)\n    at Object.\u003canonymous\u003e (/Users/zavr/adc/erotic/example/assert-node.js:13:3)\n    at Module._compile (module.js:653:30)\n    at Module._compile (/Users/zavr/adc/erotic/node_modules/pirates/lib/index.js:99:24)\n    at Module._extensions..js (module.js:664:10)\n    at Object.newLoader [as .js] (/Users/zavr/adc/erotic/node_modules/pirates/lib/index.js:104:7)\n    at Module.load (module.js:566:32)\n    at tryModuleLoad (module.js:506:12)\n    at Function.Module._load (module.js:498:3)\n```\n\nWhereas when using `erotic` to create a transparent error stack, the will be no indication of what happens inside the function, which can make things clearer.\n\n```js\nimport erotic from 'erotic'\n\nconst assertEqual = (actual, expected) =\u003e {\n  const e = erotic(true)\n  if (actual != expected) {\n    const er = e(`${actual} != ${expected}`)\n    throw er\n  }\n}\n\n(function test() {\n  try {\n    assertEqual('hello', 'world')\n  } catch ({ stack }) {\n    console.log(stack)\n  }\n})()\n```\n\n```\nError: hello != world\n    at test (/Users/zavr/adc/erotic/example/assert.js:13:5)\n    at Object.\u003canonymous\u003e (/Users/zavr/adc/erotic/example/assert.js:17:3)\n```\n\n\n\n## Copyright\n\nLogo: [Thornton’s Temple of Flora][2]\n\n(c) [Art Deco][1] 2019\n\n[1]: https://artd.eco\n[2]: https://publicdomainreview.org/2015/03/11/sex-and-science-in-robert-thorntons-temple-of-flora/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fartdecocode%2Ferotic","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fartdecocode%2Ferotic","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fartdecocode%2Ferotic/lists"}