segmentio / analytics-node

The hassle-free way to integrate analytics into any node application.

Home Page:https://segment.com/libraries/node

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

error thrown in callback function is caught by `flush`'s promise `.catch()`

yo1dog opened this issue · comments

commented

If a callback function in the messages queue or the callback function passed to flush throws an error, it will trigger the .catch() handler in the axios promise chain and run the thrown error through the error handling code meant for axios errors. This also results in the callback being called twice. For example:

let timesCalled = 0;
analytics.track({...}, val => {
  console.log('Call', ++timesCalled);
  throw new Error('oops');
});
Call 1
Call 2
(node:14657) UnhandledPromiseRejectionWarning: Error: oops
    at REPL1:1:124
    at /home/mike/work/ladder-services/node_modules/analytics-node/index.js:264:37
    at Array.forEach (<anonymous>)
    at done (/home/mike/work/ladder-services/node_modules/analytics-node/index.js:264:17)
    at /home/mike/work/ladder-services/node_modules/analytics-node/index.js:297:9
    at processTicksAndRejections (internal/process/task_queues.js:95:5)
  1. axios request finishes and the promise is resolved.
  2. .then() is called (line 289).
  3. done() is called.
  4. Callback is called.
  5. "Call 1" printed.
  6. Callback throws error.
  7. The .then() promise is rejected.
  8. .catch() is called (line 290).
  9. done() is called again.
  10. Callback is called again.
  11. "Call 2" printed.
  12. Callback throws error again.
  13. The .catch() promise is rejected.
  14. Nothing handles the rejected promise so UnhandledPromiseRejection.

This is equivalent to:

try {
  await axios.post(...);
  done();
} catch(err) {
  done(err);
}

To fix, you should break onto a new callstack using setImmediate before calling the callbacks:

const done = err => {
  setImmediate(() => {
    callbacks.forEach(callback => callback(err))
    callback(err, data)
  })
}