Brett Zamir
Brett Zamir

Reputation: 14345

Can a promise resolver/rejecter trigger its opposite?

If we have a promise like the following, what are the answers to the questions in its comments?

p.then(function ok () {
    // Can we get err() to trigger from inside here?
}, function err () {
    // Can we get ok() to trigger from inside here?
});

I know that one can attach a new then which can wait for the results or reverse the results of p, but I'm wondering, assuming that p is constant, whether the conditions can call each other recursively also (and without assigning the functions to variables and invoking them by name)...

UPDATE

My use case is as follows.

In IndexedDB, when opening a database, you can listen for the following:

db.onsuccess = ...
db.onerror = ...
db.onblocked = ...

db.js, a library I'm expanding to meet my needs, adds these events with a Promise API, such that a success will resolve the promise, and errors or blocking will reject it.

A common use case for listening for blocking would be to close the database connection which is causing the blocking and IndexedDB will thereupon automatically call onsuccess. The problem is that if we treat onblocked as a rejection, it apparently has no way to retrigger the resolve condition (i.e., the onsuccess). In order to get around this, I can have the blocking be supplied instead as a callback, but I was wondering whether there were any way to do it exclusively using the Promises approach since technically, the error would no longer be an error and would like to give a chance for the original resolve callback to resume with its handling of success.

Upvotes: 0

Views: 937

Answers (2)

Bergi
Bergi

Reputation: 664513

Can we get err() to trigger from ok?
Can we get ok() to trigger from err?

No. The promises spec mandates that at most one of the two then handlers gets called. If the promise fulfills the first gets called, if the promise rejects the second will, and a promise can't do both things.

So while you could easily create two named functions and call each other manually, it is not programmatically possible to trigger the promise into calling the other.

My use case is as follows […]

What you actually seem to be looking for is

db.open().catch(function(err) {
    if (err.isBlocking)
        return db.closeAndWaitForNextSuccess(err.blocker); // get another promise
    else
        throw err;
}).then(function ok(res) {
    …
});

Upvotes: 2

Michael Zlatkovsky
Michael Zlatkovsky

Reputation: 8670

Because you're specifying the error handler as a second parameter to .then, what it semantically means is that it will only trigger if the promise "p" got rejected. So the two paths are mutually exclusive. It also means that if inside your "ok" handler you throw an error, it will NOT be caught by the "err" function.

By contrast, if your code used a .catch, it would catch both an error bubbling up from the promise's rejection, and from within the success handler. So if you had this:

p.then(function () {
    throw new Error("Within the .then");
}).catch(function (error) {
    console.log(error);
});

It would always log the error to the console, regardless of whether p had resolved or rejected.

So for your first question: the ok handler could trigger err function, IF instead of doing .then with two arguments you do .then(successHandler).catch(errorHandler). But for the second question it's "no" no matter what -- there is no logical path from either an error handler or a "catch" to a path that was explicitly skipped due to the rejection.

Update based on updated question description:

Assuming that you get some sort of indicator in the "catch" on why the error occurred (and whether you should really reject or whether you should continue as if it's a success), there's no reason why you couldn't call the same function as you would for success. It's just that it would have two entry points:

p.then(successHandler, failureHandler)
 .then( /* other stuff if you want to chain */ )
 .catch(function(e) {
    // catch-all handler just in case
 })

function successHandler(data) {
    // Do something. Note that to avoid 
    // "breaking the promise chain", the code here
    // should either be all-synchronous or return another promise.
}

function failureHandler(error) {
    if (error.wasDueToBlocking()) {
        return successHandler(error.partialData);
    } else {
        // handle the true error. Again, to avoid 
        // "breaking the promise chain", the code here
        // should either be all-synchronous or return another promise.
    }
}

Update 2 based on requirement to not create standalone named function/variables

I'm not entirely sure why you wouldn't want named functions, and the approach I'm about to show is perhaps a little strange. But conceivably you could do this, instead, with the success handler not doing anything other than chaining through to an actual common handler.

p.then(function(data) {
    return {goOn: true, data: data};
 }, function(error) {
    if (error.wasDueToBlocking()) {
        return {goOn: true, data: error.partialData};
    } else {
        // Do some errorHandling logic
        return {goOn: false};
    }
 }
 .then(function(passedThroughData) {
    if (passedThroughData.goOn) {
        // Do something with it.
        // Otherwise ignore, since error handler
        // already took care of whatever it needed above
    }
 })
 .then(function() {
    // whatever you wanted to happen next
 })
 .catch(function(e) {
    // catch-all handler just in case
 })

Upvotes: 2

Related Questions