12

I have the following promise chain below and it appears to be quite messy (Each _create* function returns a promise):

return new Promise(function (resolve, reject) {
      _this.database.transaction(function (t) {
        _this._createExternalAccount(payment, t)
          .then(function (externalAccount) {
            return _this._createExternalTransaction(externalAccount, payment, t)
              .then(function (externalTransaction) {
                return _this._createAddress(externalAccount, payment, t)
                  .then(function (address) {
                    return _this._createTransaction(address, payment, t)
                      .then(function (transaction) {
                        return _this._createGatewayTransaction(externalTransaction, transaction, payment, t)
                          .then(function (gatewayTransaction) {
                            t.commit();
                            resolve(bridgePayment);
                          });
                      });
                  });
              });
          })
          .error(function (bridgePayment) {
            t.rollback();
            reject(bridgePayment);
          });
      });

I know there are Promise functions I can use like all() and join() but these seem to run the functions concurrently which I can't do because persisting to some tables require fields from the previously persisted tables. I'm hoping there is some way for me to do something like the following but I can't seem to find out how:

Promise.all(_this._createExternalAccount(payment, t), _this._createExternalTransaction(externalAccount, payment, t), _this._createAddress(externalAccount, payment, t))
    .then(function(externalAccount, externalTransaction, address) {
        // do logic
    });
2
  • 3
    You may want to explore chaining: p.then().then().then().then() rather than nesting. Commented Oct 10, 2014 at 18:43
  • Check Bluebird .map() you can pass {concurrency: N} so it only performs N operations at a time Commented Oct 10, 2014 at 19:33

2 Answers 2

6

I'm exactly sure what you're asking but.

  1. If you want to run an array of promises sequentially there's this answer

    The important thing to note is it's not an array of promises. It's an array of functions that make a promise. That's because promises execute immediately so you can't create the promise until you need it.

  2. If you don't want to put them in array though the normal thing is just chain them normally.

    Again the easiest way to to make a bunch functions the return promises. Then you just

    var p = firstFunctionThatReturnsAPromise()
    .then(secondFunctionThatReturnsAPromise)
    .then(thirdFunctionThatReturnsAPromise)
    .then(fourthFunctionThatReturnsAPromise)
    

    You can nest them just as easily

    function AOuterFunctionThatReturnsAPromise() {         
        var p = firstFunctionThatReturnsAPromise()
                .then(secondFunctionThatReturnsAPromise)
                .then(thirdFunctionThatReturnsAPromise)
                .then(fourthFunctionThatReturnsAPromise);
        return p;
    };
    

    Now that outer function is just another function returning a promise which means you can apply same pattern as the inner functions.

    If course those can be inline

    var p = function() {
      return new Promise(resolve, reject) {
        DoSomethingAsync(function(err, result) {
          if (err) {
            reject();
          } else {
            resolve(result);
        };
      };
    }).then(function() {
      return new Promise(resolve, reject) {
        DoSomethingAsync(function(err, result) {
          if (err) {
            reject(err);
          } else {
            resolve(result);
        };
      };
    }).then(function() {
      var err = DoSomethingNotAsync();
      if (err) {
         return Promise.reject(err);
      } else {
         return Promise.resolve();
      }
    });
    

    etc...

Sign up to request clarification or add additional context in comments.

Comments

3

Personally, when things get messy with dependencies I prefer the following approach:

var externalAccount     = Promise.join(payment, t,                                   createExternalAccount),
    externalTransaction = Promise.join(externalAccount, payment, t,                  createExternalTransaction),
    address             = Promise.join(externalAccount, payment, t,                  createAddress),
    transaction         = Promise.join(address, payment,                             createTransaction),
    gatewayTransaction  = Promise.join(externalTransaction, transaction, payment, t, createGatewayTransaction);

Makes everything much cleaner, though it's a matter of style.

And if you want to do something after you get gatewayTransaction's value (asynchronously, of course), you can just:

gatewayTransaction
    .then(function (val) {
        // do stuff
    })
    .catch(function (err) {
        // do stuff
    });

There's one subtle pitfall here that you should be aware of. The order in which the promises are defined is not necessarily the order in which the functions are called. This is what the dependencies look like:

externalAccount -----> externalTransaction -----> gatewayTransaction
                |--> address --> transaction --|

Though this is good for performance, you might want to make the whole thing sequential (just like your callback pyramid). In this case, you can write:

var externalAccount     = Promise.join(payment, t,                                       createExternalAccount),
    externalTransaction = Promise.join(externalAccount, payment, t,                      createExternalTransaction),
    address             = Promise.join(externalAccount, payment, t, externalTransaction, createAddress),
    transaction         = Promise.join(address, payment,                                 createTransaction),
    gatewayTransaction  = Promise.join(externalTransaction, transaction, payment, t,     createGatewayTransaction);

By adding externalTransaction to address's dependencies (even though its value isn't needed), you can force it to be sequential.

2 Comments

This is definitely what I had in mind when we discussed adding join to Bluebird with Gorgi and Petka. I think it's a nice style
Glad to hear, I really like it.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.