0

So I thought I can use Promise.method() from bluebird to replace the trycatch lib I've been using.

Unfortunately, it seems to not catch a thrown error from a setTimeout.

I have something along these lines

function run()
{
    var p = Promise.pending()

    var inner = Promise.method(function()
    {
        //some code that could potentially get stuck

        setTimeout(function $timeoutTaskKill() {
            if (p.promise.isPending())
            {
                var duration = moment.duration(taskTimeout).seconds();
                throw new Error(util.format('timeout has been reached: %ss', duration));
            }
        }, taskTimeout)
    });

    //pseudo
    inner().then(p.reject, p.resolve);

    return p.promise;
}

It crashes my process. When I used the trycatch library instead of Promise.method, it caught the error.

Madd0g
  • 3,841
  • 5
  • 37
  • 59
  • Is that "pseudo" really only pseudo or are you actually using the [deferred antipattern](http://stackoverflow.com/q/23803743/1048572)? – Bergi Nov 03 '14 at 21:37
  • I am using the anti-pattern. I am acutely aware of everywhere where I don't use "return" with promises. This is one of the only places. Does it matter in regards to the error handling? – Madd0g Nov 03 '14 at 21:41
  • Note you're using the `.then(success,fail)` antipattern as well as deferred anti pattern. – Benjamin Gruenbaum Nov 03 '14 at 22:13
  • @BenjaminGruenbaum - that's why I put the pseudo there, it's just to show what actually happens when inner ends, the outer promise is fulfilled/rejected. – Madd0g Nov 03 '14 at 22:22
  • See also [Using Q.promises: how to catch an async throw?](http://stackoverflow.com/q/15504429/1048572) and [Asynchronous exception handling with bluebird promises](http://stackoverflow.com/q/25143476/1048572) – Bergi Jul 15 '15 at 20:51

1 Answers1

2

trycatch uses domains to catch such errors, promises don't.

To get a rejected promise, you need to do so explicitly. That might be done by throwing from a promise (then) handler, but not from an arbitrary asynchronous callback.

What you can do:

  • Actually reject the promise from the callback:

    setTimeout(function $timeoutTaskKill() {
        var duration = moment.duration(taskTimeout).seconds();
        p.reject(new Error(util.format('timeout has been reached: %ss', duration)));
    }, taskTimeout)
    
  • Use promises. Always promisify at the lowest possible level, in this case the setTimeout. Actually, Bluebird has done this for you already: Promise.delay.

    Promise.race([
        actualToDo(),
        Promise.delay(taskTimeout).then(function() {
            var duration = moment.duration(taskTimeout).seconds();
            throw new Error(util.format('timeout has been reached: %ss', duration));
        })
    ])
    
  • Or use Bluebird's built-in timeout() method.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • huh. Can I do `Promise.fulfilled().then()` and do it from the then section?? Isn't there an easier way? EDIT: oops. just saw your edit – Madd0g Nov 03 '14 at 21:46
  • @Madd0g You can just do `inner().timeout(taskTimeout)` and get the exact same result as you have with all that code only with better types and performance. This is what bergi means with `timeout()`. – Benjamin Gruenbaum Nov 03 '14 at 22:15
  • Bergi - it might be worth mentioning that promises are throw safe and that in order to get that throw safety you must work against a promise API. – Benjamin Gruenbaum Nov 03 '14 at 22:16
  • I thought that's what `Promise.method` did, made the function "throw safe" – Madd0g Nov 03 '14 at 22:17
  • @BenjaminGruenbaum I said that `then`-handlers are throw-safe (and of course, everything derived from them). – Bergi Nov 03 '14 at 22:18
  • `Promise.then(function(foo){ setTimeout(function(bar){ throw "nein"; }); });` – Benjamin Gruenbaum Nov 03 '14 at 22:18
  • I want to solve the throw problem, not the timeout problem, I'm using throw in other places and wanted to just try to refactor out the trycatch library – Madd0g Nov 03 '14 at 22:18
  • @Madd0g: No, `Promise.method` only make the *current* function throw-safe. If you throw from an unrelated (asynchronous) function *which is not called from `Promise.method`* (but from the async task), this cannot be caught. If you have multiple occurences of this, use my second approach - promisify at a lower level. – Bergi Nov 03 '14 at 22:20
  • I find it quite unreasonable that I keep getting answers like "this type of error cannot be caught" whenever I step out of my promises comfort zone (and I'm using `throw` all the time, I live in my secluded little world protected by promises). How can domains be the only answer? Anyway, I solved the timeout thing with the `Promise.delay()` method. couldn't get `timeout()` to error. – Madd0g Nov 03 '14 at 23:52
  • funny, last time I asked for help on a [similar problem](http://stackoverflow.com/questions/24194808/bluebird-promisify-and-callback-with-no-error-argument) I got the "impossible to catch" answer from @BenjaminGruenbaum. – Madd0g Nov 04 '14 at 00:00
  • 1
    Looks like [the experts](http://stackoverflow.com/tags/promise/topusers) agree on this :-) The (quite reasonable) point is that `throw` does return control synchronously to its *caller* - which, in an asynchronous callback, is not the code that initiated the asynchronous task, but the event loop. The only solution to catch these is to put the handler between the event loop and the callback, and link it to an error callback supplied by the task starter code - that's just what domains do. – Bergi Nov 04 '14 at 00:11
  • What makes code in `then()` throw-safe? Why couldn't I achieve that by promisifying (in the linked question)? Is the current problem just with the setTimeout part because the timer has no "caller"? It's horrifying to understand how much you don't know sometimes – Madd0g Nov 04 '14 at 00:21
  • 1
    Code in `then`-callbacks is throw-safe because the promise library catches exceptions for you - which is possible because it knows what to do with a caught error: reject the returned promise. Promisification relies on getting errors passed to its node callback, it cannot detect arbitrary exceptions - because it isn't the caller of the async callback. Yes, that's the problem with the `setTimeout` - the timer (event loop) that calls the callback doesn't know what to do with exceptions that bubbled up to it, the only thing it can do is to cause a global exception. – Bergi Nov 04 '14 at 00:54