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
    });
trooper
  • 4,444
  • 5
  • 32
  • 32
Conner McNamara
  • 365
  • 1
  • 5
  • 15

2 Answers2

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...

Community
  • 1
  • 1
gman
  • 100,619
  • 31
  • 269
  • 393
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.

ldmat
  • 935
  • 1
  • 6
  • 15