[BUG] Callback is part of promise chain
What / Why
The promise support introduced in 2.0.1 has a side-effect on the callback interface: the callback becomes part of the promise chain. If the callback throws, an unhandled promise rejection follows. With node's current default behavior (of not crashing) this is problematic.
When
Using which >= 2.0.1 with the callback interface.
How
Current Behavior
https://github.com/npm/node-which/blob/8ef60698398872699b5446a06fc579d281a94b91/which.js#L84
Steps to Reproduce
which('foo', function () {
throw new Error('Something went wrong outside of the domain of `which`')
})
Expected Behavior
There are two possible solutions:
- Call the callback in a next tick, to escape the promise chain
- Don't create a promise if a callback is passed in; instead follow this pattern:
function example (callback) {
if (!callback) {
var promise = function (resolve, reject) {
callback = function (err, res) {
if (err) reject(err)
else resolve(res)
}
}
}
// ..
return promise
}
Would you take a PR for either?
So something like this, then?
diff --git a/which.js b/which.js
index 82afffd..1aa94c1 100644
--- a/which.js
+++ b/which.js
@@ -40,6 +40,8 @@ const getPathInfo = (cmd, opt) => {
}
}
+const escapePromise = fn => (...args) => process.nextTick(() => fn(...args))
+
const which = (cmd, opt, cb) => {
if (typeof opt === 'function') {
cb = opt
@@ -48,6 +50,9 @@ const which = (cmd, opt, cb) => {
if (!opt)
opt = {}
+ if (cb)
+ cb = escapePromise(cb)
+
const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt)
const found = []
Not creating a promise when a cb is passed in would be difficult, because we're using resolve/reject to tie the step/substep calls together, so it either resolves with a value or another promise.
Anyway, yeah, nextTick is fine. Just add a test to that patch, and I'll accept it. :)