Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to handle nested Promises (bluebird) [duplicate]

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
    });
like image 290
Conner McNamara Avatar asked Oct 10 '14 17:10

Conner McNamara


People also ask

How do you handle nested promises?

In a promise nesting when you return a promise inside a then method, and if the returned promise is already resolved/rejected, it will immediately call the subsequent then/catch method, if not it will wait. If promised is not return, it will execute parallelly.

How many promises can promise all handle?

all itself as a promise will get resolved once all the ten promises get resolved or any of the ten promises get rejected with an error.

Does promise all resolve promises in order?

Here, Promise. all() method is the order of the maintained promises. The first promise in the array will get resolved to the first element of the output array, the second promise will be a second element in the output array and so on.

Can a promise have multiple resolve?

No. It is not safe to resolve/reject promise multiple times. It is basically a bug, that is hard to catch, becasue it can be not always reproducible.


2 Answers

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

like image 98
gman Avatar answered Nov 11 '22 16:11

gman


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.

like image 43
ldmat Avatar answered Nov 11 '22 14:11

ldmat