node-which icon indicating copy to clipboard operation
node-which copied to clipboard

[BUG] Callback is part of promise chain

Open vweevers opened this issue 6 years ago • 2 comments

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:

  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?

vweevers avatar Nov 16 '19 11:11 vweevers

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 avatar Nov 18 '19 22:11 isaacs

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

isaacs avatar Nov 18 '19 22:11 isaacs