Failing, selectively

14 views
Skip to first unread message

norm...@gmail.com

unread,
Jul 29, 2013, 11:01:32 PM7/29/13
to q-con...@googlegroups.com
I'm creating a deferrable whose promise will be passed back to the user. When the deferrable is rejected, I want to filter which errors get handed down to the user. In my case, some errors are handled by the library itself rather than 

Here's what I have:

p1 = defer.promise.fail(function(e) {
if (e instanceof RpcFailure) {
   throw e;
} else {
## Handle the error internally
}
});

If the defer is rejected with an RpcFailure, the .fail clause re-throws the exception, and p1 fails. So far, so good. If the defer is reject with something else; however, the "internal error handling" is triggered, which returns some value. This causes p1 to succeed. Now the user is getting a promise that succeeds when an error occurs!

If my .fail could return a new promise, one that remains untouched, I think this would accomplish what I'm after. I'm just not sure how to create a "dummy promise". Or is there a better way? Something more clever?

Hope this makes sense, thanks!

Norman

Kris Kowal

unread,
Jul 29, 2013, 11:20:41 PM7/29/13
to Q Continuum

It might be helpful to consider how you would accomplish this with the synchronous analogue.

Your requirement appears to be that you want your function to communicate the following possible results:

1. succeed with a value
2. fail with an error (if the cause is an RPC error)
3. succeed and/or fail (if the cause is not an RPC error)

The crux of it is path 3. You seem to want more than one "result".  If that’s the case, you’ll probably have to bundle your result in an envelope, or emit ancillary information on a side-channel.

Once you resolve the type of the p1 result in such a way that it can convey all of the information you want, the solution will pour from your fingers.

Consider this interpretation:
1. succeed with a {value, error: null}
2. succeed with {value: null, error}
3. succeed with {value, error}

return promise.then(function (value) {
}, function (error) {
    if (error.rpc) {
        return {value: null, error: error};
    } else {
        var value = handleInternally();
        return {value: value, error: error};
    }
});

Or this interpretation:
1. succeed with a value
2. fail with an error (for RPC errors only)
3. succeed or fail depending on the result of the error handler in all other cases, but leave a note that it had to try really hard

return promise.catch(function (error) {
    if (error.rpc) {
        throw error;
    } else {
        notify(error);
        return retry();
    }
});

Also, since you mentioned RPC, I get one free pass to point you in the direction Q-Connection[1]. It’s like RPC, but uses promises as a message passing proxy through which you can pipeline to get around common chatty protocol / latency problems with RPC.

Kris Kowal


norm...@gmail.com

unread,
Jul 29, 2013, 11:40:46 PM7/29/13
to q-con...@googlegroups.com, kris....@cixar.com
Kris,

Thanks for your thorough reply! You are correct that I want to handle errors differently depending on the type, but maybe I should elaborate a bit more. P1 should only fail if the deferrable is rejected with an RpcFailure. Any other failure should be silently swallowed, such that P1 neither succeeds nor fails.

Here's what I'm doing (a bit wordy)... A Javascript websocket connects back to an RPC server. From various points on the site (it's a Backbone / Chaplin application), my code initiates RPC requests. My library is returning a promise ("p1"). Take, for instance, an RPC request to authenticate a user. If the credentials are valid, the success is passed back to p1. If the credentials are bogus, it returns an RpcFailure and p1 fails.

If; however, the server experiences some sort of unexpected error (database unavailable, coding error, etc), this is represented as an RpcError. This is a much more serious problem, and one that I do not want to have to handle for every single RPC call throughout the site. So instead of p1 succeeding or failing, I want to pop up a dialog box explaining that an unexpected error has occurred. At this point, the application can recover by refreshing the page, or in the case of Chaplin, reinitializing the view. This will build all new RPC requests, with new promises, and try again.

I actually hacked the desired behavior by having my .fail return Q.defer().promise in order to prevent p1 from being triggered. But this seems a bit of a hack, no?

I did briefly check out Q-Connection, but should revisit it now that I'm farther along in this project.

Thanks again for all your help,

Norman

Kris Kowal

unread,
Jul 29, 2013, 11:59:14 PM7/29/13
to Q Continuum
Sounds to me like you just need to catch the error at a layer above where the fulfillment value is handled.

Separate your code into:
1. the layer that interacts with the rpc server
2. the layer that handles the results
3. a layer that catches any errors that full from above and prompts the user

function rpc() {
    // return promise
}

function handle() {
    return Q.fcall(rpc)
    .catch(function (error) {
        if (error.rpc) {
            throw error;
        } else {
            return handle();
        }
    })
}

function trampouline() {
    return Q.fcall(handle)
    .catch(promptUser);
}

Kris Kowal
Reply all
Reply to author
Forward
0 new messages