1

I'm trying to pass through intermediate values between steps in a promise, and I can't find a clean way to do so. This seems pretty common as a use case, so I'm hoping I'm just missing a pattern, rather than being totally off track.

I'm using Bluebird for promises, with Sequelize (SQL ORM).

Example code:

db.sync().then(function () {
    // When DB ready, insert some posts
    return [
        BlogPost.create(),
        BlogPost.create()
    ];
}).spread(function (post1, post2) {
    // Once posts inserted, add some comments
    return [
        post1.createComment({ content: 'Hi - on post 1' }),
        post2.createComment({ content: 'Hi - on post 2' })
    ];
}).spread(function (post1, post2) { // THE PROBLEM: Want posts here, not comments
    // Do more with posts after comments added, e.g. add tags to the posts

    // Can't do that in the above as something needs to wait for
    // comment creation to succeed successfully somewhere.

    // Want to wait on Comments promise, but keep using Posts promise result
});

The best solution I have so far is:

db.sync().then(function () {
    // When DB ready, insert some posts
    return [
        BlogPost.create(),
        BlogPost.create()
    ];
}).spread(function (post1, post2) {
    // Once posts inserted, add some comments
    return Promise.all([
        post1.createComment({ content: 'Hi - on post 1' }),
        post2.createComment({ content: 'Hi - on post 2' })
    ]).then(function () {
        // Extra nested promise resolution to pull in the previous results
        return [post1, post2];
    });
}).spread(function (post1, post2) {
    // Do things with both posts
});

Surely there's a better way though? Bluebird has .tap(), which is pretty close, but doesn't do the spread() part, and I can't find an easy way to combine then.

Tim Perry
  • 11,766
  • 1
  • 57
  • 85

2 Answers2

1

I closed this but then reopened since your issue is a lot more specific than the generic one. Make sure your read this question and answers on the general issue.

For a specific context of one-action-above - you can use .return with bluebird to override the return value:

db.sync().then(function () {
    ...
}).spread(function (post1, post2) {
    return Promise.all([
        post1.createComment({ content: 'Hi - on post 1' }),
        post2.createComment({ content: 'Hi - on post 2' })
    ]).return([post1, post2]); // use .return here
}).spread(function (post1, post2) { comments
   // posts here
});
Community
  • 1
  • 1
Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • A variant of this would be not to spread. By using `.then(function (posts) {...}` you can `return Promise.all(...).return(posts)` without having to rebundle the original data. Of course, you would have to do `posts[0].createComment(...)` and `posts[1].createComment(...)` but maybe that's a lesser evil. Not a big deal either way - at least not with a known small number of posts. – Roamer-1888 Jun 24 '15 at 17:44
0

After some more investigation, I've found a better answer still (with 0 promise nesting):

db.sync().then(function () {
    // When DB ready, insert some posts
    return [
        BlogPost.create(),
        BlogPost.create()
    ];
}).all().tap(function (posts) {
    // Once posts inserted, add some comments
    return [
        posts[0].createComment({ content: 'Hi - on post 1' }),
        posts[1].createComment({ content: 'Hi - on post 2' })
    ]
}).spread(function (post1, post2) {
    // Do things with both posts
});

Note the .all() before the tap: this ensures the result of the first then() is unwrapped correctly before the tap() is reached, and allows you to use the tap as normal. Still doesn't give you spread() support, but it's close enough.

Tim Perry
  • 11,766
  • 1
  • 57
  • 85