Nested Promise execution is out of sync

2019-08-02 03:16发布

问题:

I have a simple setup, illustrated in this fiddle:

var doDelay = function(who) {
    return Promise.delay(50)
        .tap(function() {
            console.log(who + ' done');
        });
};

Promise.resolve()
    .then(doDelay('a'))
    .tap(function() {
        console.log('a done2');
    })
    .then(doDelay('b'))
    .tap(function() {
        console.log('b done2');
    })
    .then(function() {
        console.log('all done!');
    });

The output is:

a done2
b done2
all done!
a done
b done

But I expect:

a done
b done
a done2
b done2
all done!

What am I doing wrong?

回答1:

As Bergi points out, it's not valid to pass a promise as the argument to then, but even if it were allowed, another issue is that as soon as you call delayMany('a'), the Promise.delay(50) inside it will start executing which is not what you are looking to do.

You can either wrap the call in an anonymous function, or use .bind():

var delayMany = function(who) {
    return Promise.delay(50)
    .tap(function() {
        console.log(who  + ' done');
    });
};

Promise.resolve()
.then(delayMany.bind(null, 'a'))
.tap(function() { console.log('a done2'); })

.then(delayMany.bind(null, 'b'))
.tap(function() { console.log('b done2'); })

.then(function() {
    console.log('all done!');
});

Q: "Why doesn't then throw an error when I pass a promise to it?"

The answer to this is in the Promises/A+ spec:

A promise’s then method accepts two arguments:

promise.then(onFulfilled, onRejected)

2.2.1. Both onFulfilled and onRejected are optional arguments:

2.2.1.1. If onFulfilled is not a function, it must be ignored.



回答2:

You must not pass a promise (the result of immediately calling delayMany) to then. Instead, it expects a callback function to execute when the context promise resolves. You'll need to wrap your delayMany invocation in a function expression:

Promise.resolve()
.then(function(undefined) { return delayMany(xsa); })
.tap(function(xsa) { console.log(xsa[0] + ' done2'); })
.then(function(xsa) { return delayMany(xsb) })
.tap(function(xsb) { console.log(xsb[0] + ' done2'); })
.then(function(xsb) { console.log('all done!'); });


回答3:

Since you are using bluebird you can simply use the .delay method instead:

Promise.resolve()
    .delay('a', 50)
    .tap(function() {
        console.log('a done2');
    })
    .delay('b', 50)
    .tap(function() {
        console.log('b done2');
    })
    .then(function() {
        console.log('all done!');
    });