Home  >  Q&A  >  body text

Rejections are not propagated in chained promises

I can't understand why the rejection isn't passed through the promise chain, and I'm hoping someone can help me understand why. To me, attaching a function to a sequence of promises means I'm relying on the intent of the original promise to be fulfilled. This is hard to explain, so let me first show a code example of my problem. (Note: This example uses Node and the delayed node module. I tested this using Dojo 1.8.3 and got the same results)

var d = require("deferred");

var d1 = d();

var promise1 = d1.promise.then(
    function(wins) { console.log('promise1 resolved'); return wins;},
    function(err) { console.log('promise1 rejected'); return err;});
var promise2 = promise1.then(
    function(wins) { console.log('promise2 resolved'); return wins;},
    function(err) { console.log('promise2 rejected'); return err;});
var promise3 = promise2.then(
    function(wins) { console.log('promise3 resolved'); return wins;},
    function(err) { console.log('promise3 rejected'); return err;});
d1.reject(new Error());

The result of running this operation is this output:

promise1 rejected
promise2 resolved
promise3 resolved

Well, to me, this result doesn't make sense. By appending to this Promise chain, each then implies the intention that it will depend on the successful resolution of d1 and the result being passed along the chain. If the promise in promise1 does not receive a wins value, but an err value in its error handler, how can the next promise in the chain call its success function? It cannot pass a meaningful value to the next Promise because it does not get the value itself.

I can describe my idea another way: There are three people: John, Ginger and Bob. John owns a widget store. Ginger walked into his store and asked for a bag of widgets in various colors. He didn't have them in stock, so he sent a request to his dealer to have them shipped to him. Meanwhile, he gives Ginger a rain check, saying he owes her the bag of widgets. Bob spots Ginger getting the widgets and asks him to get the blue widget when she's done with them. She agreed and gave him a note saying she would agree. Now, John's dealer cannot find any widgets in their supply and the manufacturer no longer makes the widgets, so they notify John, who in turn notifies Ginger that she cannot obtain the widgets. How can Bob get the blue widget from Ginger when he doesn't get anything himself?

My third, more realistic view on this issue is this. Let's say I have two values ​​that I want to update to the database. One depends on the id of the other, but I can't get the id until I insert it into the database and get the result. On top of that, the first insert depends on the database query. The promise returned by the database call is what I use to chain the two calls into a sequence.

var promise = db.query({parent_id: value});
promise.then(function(query_result) {
    var first_value = {
        parent_id: query_result[0].parent_id
    }
    var promise = db.put(first_value);
    promise.then(function(first_value_result) {
        var second_value = {
            reference_to_first_value_id: first_value_result.id
        }
        var promise = db.put(second_value);
        promise.then(function(second_value_result) {
            values_successfully_entered();
        }, function(err) { return err });
    }, function(err) { return err });
}, function(err) { return err });

Now, in this case, if db.query fails, it will call the first then's err function. But then it calls the success function of the next promise. Although this Promise expects the result of the first value, it gets an error message from its error handling function.

So, my question is, if I have to test for errors in the success function, why do I have an error handling function?

Sorry, this article is too long. I just don't know how to explain it another way.

Updates and Corrections

(Note: I deleted a reply I had made to some comments. So if someone commented on my reply, their comment might appear out of context now that I've deleted it. Sorry about that , I tried to keep it as short as possible.)

thanks for your replies. I first want to apologize to everyone for how poorly my question was written, especially my pseudocode. I was a little too aggressive in trying to keep it short.

Thanks for the reply Bergi, I think I found the error in my logic. I think I may be overlooking another issue that is causing the problems I'm having. This may cause the promise chain to work differently than I thought. I'm still testing different elements of the code, so I can't even form a proper question to see what I'm doing wrong. However, I did want to update everyone on the situation and thank you for your help.

P粉193307465P粉193307465362 days ago2824

reply all(2)I'll reply

  • P粉155710425

    P粉1557104252023-10-24 16:07:16

    @Jordan First, as commenters pointed out, your first example will definitely produce the results you expect when using a lazy library:

    promise1 rejected
    promise2 rejected
    promise3 rejected

    Secondly, even if it produces the output you suggest, it does not affect the execution flow of the second code snippet, which is a bit different and more like:

    promise.then(function(first_value) {
        console.log('promise1 resolved');
        var promise = db.put(first_value);
        promise.then(function (second_value) {
             console.log('promise2 resolved');
             var promise = db.put(second_value);
             promise.then(
                 function (wins) { console.log('promise3 resolved'); },
                 function (err) { console.log('promise3 rejected'); return err; });
        }, function (err) { console.log('promise2 rejected'); return err;});
    }, function (err) { console.log('promise1 rejected'); return err});

    And, if the first promise is rejected, only output:

    promise1 rejected

    However (getting to the most interesting part) even though the deferred library definitely returns 3 x returned, most other promise libraries will return 1 x returned, 2 x resolved (This leads to the assumption that you obtain these results by using some other Promise library).

    Also confusing is that other libraries behave more correctly. Let me explain.

    In the synchronous world, the counterpart to "promise rejection" is throws. So semantically speaking, async deferred.reject(new Error()) in sync is equal to throw new Error(). In your example you don't throw errors in the synchronous callback, you just return them, so you switch to the success flow where errors are the success values. To ensure rejection of further passes, you need to re-throw the error:

    function (err) { console.log('promise1 rejected'); throw err; });

    Now the question is, why does the delay library treat the returned error as a rejection?

    The reason is that rejection in delayed work is a little different. In the deferred lib, the rules are: When an error instance occurs, the promise will be rejected , so even if you do deferred.resolve(new Error()) it will work like deferred.reject(new Error()), if you try to execute deferred.reject(notAnError), it will throw an exception indicating that the Promise can only be rejected with errors. This clearly shows why the error returned from the then callback rejects the promise.

    There is some valid reasoning behind the defer logic, but it still doesn't fit with how throw works in JavaScript, so this behavior is scheduled to change in the v0.7 release of defer.

    Short summary:

    To avoid confusion and unexpected results, just follow the rules of good practice:

    1. Always reject promises with error instances (following the rules of the synchronous world, where throwing non-error values ​​is considered bad practice).
    2. Reject the synchronization callback by throwing an error (returning an error does not guarantee rejection).

    Adhere to the above and you will get consistent and expected results in Deferred and other popular Promise libraries.

    reply
    0
  • P粉376738875

    P粉3767388752023-10-24 12:09:36

    No. What you are describing is not a chain but simply attaching all callbacks to d1. However, if you want to link something using then, the result of promise2 depends on the resolution of promise1 and the resolution of promise1 and < code> then the callback handles it .

    The documentation states:

    .then Methods typically look at Promises/A specification (or more strictly Promises/A ). This means that the Promise returned by the callback shell will be assimilated to the resolution of Promise2, and if there is no success/error handler, the corresponding result will be passed directly to Promise2 code> - so you can simply omit the handler to propagate errors.

    However, if the error is handled , the resulting promise2 is considered fixed and will be fulfilled with that value. If you don't want this to happen, you must re-throw the error just like in a try-catch clause. Alternatively, you can return a (pending) rejected promise from the handler. Not sure what Dojo's rejection is, but:

    var d1 = d();
    
    var promise1 = d1.promise.then(
        function(wins) { console.log('promise1 resolved'); return wins;},
        function(err) { console.log('promise1 rejected'); throw err;});
    var promise2 = promise1.then(
        function(wins) { console.log('promise2 resolved'); return wins;},
        function(err) { console.log('promise2 rejected'); throw err;});
    var promise3 = promise2.then(
        function(wins) { console.log('promise3 resolved'); return wins;},
        function(err) { console.log('promise3 rejected'); throw err;});
    d1.reject(new Error());

    He shouldn't be able to. Without an error handler, he would only perceive the message that there are no widgets left ((from John) from Ginger). However, if Ginger sets an error handler for this situation, if John or his dealer With no blue widgets left, she can still fulfill her promise to give Bob a widget from her own cabin and give him a green widget.

    To convert the error callback into a meta, return the error from the handler like saying "If no widgets are left, just give him a comment that no widgets are left - this is the same as the required widget The parts are just as good”.

    ...This means the error has been handled there. If you don't, just omit the error callback. BTW, your success callbacks don't

    return the promise they are creating, so they appear to be useless. the correct one is:

    var promise = db.query({parent_id: value});
    promise.then(function(query_result) {
        var first_value = {
            parent_id: query_result[0].parent_id
        }
        var promise = db.put(first_value);
        return promise.then(function(first_value_result) {
            var second_value = {
                reference_to_first_value_id: first_value_result.id
            }
            var promise = db.put(second_value);
            return promise.then(function(second_value_result) {
                return values_successfully_entered();
            });
        });
    });

    Or, since you don't need a closure to access the result value of the previous callback, even:

    db.query({parent_id: value}).then(function(query_result) {
        return db.put({
            parent_id: query_result[0].parent_id
        });
    }).then(function(first_value_result) {
        return db.put({
            reference_to_first_value_id: first_value_result.id
        });
    }.then(values_successfully_entered);

    reply
    0
  • Cancelreply