Security News
Node.js EOL Versions CVE Dubbed the "Worst CVE of the Year" by Security Experts
Critics call the Node.js EOL CVE a misuse of the system, sparking debate over CVE standards and the growing noise in vulnerability databases.
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.
yarn add -E erotic
The following examples show the benefits of using erotic
.
When 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.
import { readFile } from 'fs'
const read = async (path) => {
await new Promise((resolve, reject) => {
readFile(path, (err, data) => {
if (err) return reject(err)
return resolve(data)
})
})
}
(async () => {
const path = 'non-existent-file.txt'
try {
await read(path)
} catch ({ stack }) {
console.log(stack)
}
})()
Error: ENOENT: no such file or directory, open 'non-existent-file.txt'
erotic
: Standard Modeerotic
solves the problem described in the Node.js example above by remembering the error stack at the point of where the function was called.
import { readFile } from 'fs'
import erotic from 'erotic'
const read = async (path) => {
const er = erotic() // stack has the anchor point
await new Promise((resolve, reject) => {
readFile(path, (err, data) => {
if (err) {
const e = er(err) // stack also includes this line
return reject(e)
}
return resolve(data)
})
})
}
(async function example() {
const path = 'non-existent-file.txt'
try {
await read(path)
} catch ({ stack }) {
console.log(stack)
}
})()
Error: ENOENT: no such file or directory, open 'non-existent-file.txt'
at ReadFileContext.readFile [as callback] (/Users/zavr/adc/erotic/example/read-file.js:10:19)
at read (/Users/zavr/adc/erotic/example/read-file.js:5:14)
at example (/Users/zavr/adc/erotic/example/read-file.js:21:11)
at Object.<anonymous> (/Users/zavr/adc/erotic/example/read-file.js:25:3)
erotic
: Transparent ModeA 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.
import { readFile } from 'fs'
import erotic from 'erotic'
const read = async (path) => {
const er = erotic(true)
await new Promise((resolve, reject) => {
readFile(path, (err, data) => {
if (err) {
const e = er(err)
return reject(e)
}
return resolve(data)
})
})
}
(async function example() {
const path = 'non-existent-file.txt'
try {
await read(path) // error appears to be thrown here
} catch ({ stack }) {
console.log(stack)
}
})()
Error: ENOENT: no such file or directory, open 'non-existent-file.txt'
at example (/Users/zavr/adc/erotic/example/transparent.js:21:11)
at Object.<anonymous> (/Users/zavr/adc/erotic/example/transparent.js:25:3)
The package exports the default erotic
function.
import erotic from 'erotic'
erotic(
transparent?: boolean,
): Callback
Creates 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.
When 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.
Callback(
messageOrError: string|Error,
): Error
Returns 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.
When 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.
import erotic from 'erotic'
async function wait() {
const cb = erotic()
await new Promise((_, reject) => {
setTimeout(() => {
const err = new Error('Promise timeout error.')
err.code = 'ETIMEOUT'
const error = cb(err)
reject(error)
}, 10)
})
}
(async function example() {
try {
await wait()
} catch ({ stack, code }) {
console.log(stack)
console.log(code)
}
})()
Error: Promise timeout error.
at Timeout.setTimeout [as _onTimeout] (/Users/zavr/adc/erotic/example/set-timeout.js:9:21)
at wait (/Users/zavr/adc/erotic/example/set-timeout.js:4:14)
at example (/Users/zavr/adc/erotic/example/set-timeout.js:17:11)
at Object.<anonymous> (/Users/zavr/adc/erotic/example/set-timeout.js:22:3)
ETIMEOUT
When a string is passed, an error object is created with the message internally.
import erotic from 'erotic'
async function wait() {
const cb = erotic()
await new Promise((_, reject) => {
setTimeout(() => {
const error = cb('Promise timeout error.')
reject(error)
}, 10)
})
}
(async function example() {
try {
await wait()
} catch ({ stack }) {
console.log(stack)
}
})()
Error: Promise timeout error.
at Timeout.setTimeout [as _onTimeout] (/Users/zavr/adc/erotic/example/set-timeout-string.js:7:21)
at wait (/Users/zavr/adc/erotic/example/set-timeout-string.js:4:14)
at example (/Users/zavr/adc/erotic/example/set-timeout-string.js:15:11)
at Object.<anonymous> (/Users/zavr/adc/erotic/example/set-timeout-string.js:19:3)
The erotic
also works fine even in the strict
mode.
'use strict'
import erotic from 'erotic'
const wait = async () => {
const cb = erotic()
await new Promise((_, reject) => {
setTimeout(() => {
const err = new Error('Promise timeout error.')
const error = cb(err)
reject(error)
}, 10)
})
}
(async function example() {
try {
await wait()
} catch ({ stack }) {
console.log(stack)
}
})()
Error: Promise timeout error.
at Timeout.setTimeout [as _onTimeout] (/Users/zavr/adc/erotic/example/set-timeout-strict.js:10:21)
at wait (/Users/zavr/adc/erotic/example/set-timeout-strict.js:6:14)
at example (/Users/zavr/adc/erotic/example/set-timeout-strict.js:18:11)
at Object.<anonymous> (/Users/zavr/adc/erotic/example/set-timeout-strict.js:22:3)
In the transparent mode, the stack will start where the function was called and not show any of its internals.
import erotic from 'erotic'
async function wait() {
const cb = erotic(true)
await new Promise((_, reject) => {
setTimeout(() => {
const error = cb('Promise timeout error.')
reject(error)
}, 10)
})
}
(async function example() {
try {
await wait()
} catch ({ stack }) {
console.log(stack)
}
})()
Error: Promise timeout error.
at example (/Users/zavr/adc/erotic/example/set-timeout-transparent.js:15:11)
at Object.<anonymous> (/Users/zavr/adc/erotic/example/set-timeout-transparent.js:19:3)
For 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.
There will also be an internal Node.js error stack, such as lines with Module._compile
which are not useful.
Without erotic
, the full error stack will be exposed:
const assertEqual = (actual, expected) => {
if (actual != expected) {
throw new Error(`${actual} != ${expected}`)
}
}
(function test() {
try {
assertEqual('hello', 'world')
} catch ({ stack }) {
console.log(stack)
}
})()
Error: hello != world
at assertEqual (/Users/zavr/adc/erotic/example/assert-node.js:3:11)
at test (/Users/zavr/adc/erotic/example/assert-node.js:9:5)
at Object.<anonymous> (/Users/zavr/adc/erotic/example/assert-node.js:13:3)
at Module._compile (module.js:653:30)
at Module._compile (/Users/zavr/adc/erotic/node_modules/pirates/lib/index.js:99:24)
at Module._extensions..js (module.js:664:10)
at Object.newLoader [as .js] (/Users/zavr/adc/erotic/node_modules/pirates/lib/index.js:104:7)
at Module.load (module.js:566:32)
at tryModuleLoad (module.js:506:12)
at Function.Module._load (module.js:498:3)
Whereas 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.
import erotic from 'erotic'
const assertEqual = (actual, expected) => {
const e = erotic(true)
if (actual != expected) {
const er = e(`${actual} != ${expected}`)
throw er
}
}
(function test() {
try {
assertEqual('hello', 'world')
} catch ({ stack }) {
console.log(stack)
}
})()
Error: hello != world
at test (/Users/zavr/adc/erotic/example/assert.js:13:5)
at Object.<anonymous> (/Users/zavr/adc/erotic/example/assert.js:17:3)
Logo: Thornton’s Temple of Flora
(c) Art Deco 2019
FAQs
Capture error stacks in asynchronous functions at the point of call.
The npm package erotic receives a total of 147 weekly downloads. As such, erotic popularity was classified as not popular.
We found that erotic demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Critics call the Node.js EOL CVE a misuse of the system, sparking debate over CVE standards and the growing noise in vulnerability databases.
Security News
cURL and Go security teams are publicly rejecting CVSS as flawed for assessing vulnerabilities and are calling for more accurate, context-aware approaches.
Security News
Bun 1.2 enhances its JavaScript runtime with 90% Node.js compatibility, built-in S3 and Postgres support, HTML Imports, and faster, cloud-first performance.