Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] Callback is part of promise chain #68

Closed
vweevers opened this issue Nov 16, 2019 · 2 comments · Fixed by #86
Closed

[BUG] Callback is part of promise chain #68

vweevers opened this issue Nov 16, 2019 · 2 comments · Fixed by #86

Comments

@vweevers
Copy link

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

return cb ? step(0).then(res => cb(null, res), cb) : step(0)

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:

  1. Call the callback in a next tick, to escape the promise chain
  2. 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?

@isaacs
Copy link
Contributor

isaacs commented Nov 18, 2019

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.

@isaacs
Copy link
Contributor

isaacs commented Nov 18, 2019

Anyway, yeah, nextTick is fine. Just add a test to that patch, and I'll accept it. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants