2
\$\begingroup\$

We're rewriting some parts of our application. We're trying to avoid the promise anti-pattern and do things properly, but I'm not certain that we did so.

More precisely, we are rewriting the resolver of a state using ui-router.

.state( 'catalogue', {
  // skipped on purpose not relevant content
  resolve : 
    boxCreateData : function( $stateParams, $q, Basket, Customer ) {

      var defer = $q.defer(),
          basket = null,
          customer = null;

      Basket.fromApi( $stateParams.id )
        .then( function( basket ) {
          Customer.fromApi( basket.getCustomer() )
            .then( function( customer ) {
              defer.resolve( { basket : basket, customer : customer } );
            });
        });

      return defer.promise;

    }
});

Basket.fromApi and Customer.fromApi are static methods of their corresponding "class"es, Basket and Customer. Both implementations are like this (more or less; it's not the whole code, but it shows the concept. Customer.fromApi is the same):

Basket.fromApi = function( id ) {
  var defer = $q.defer();
  boxesResource.get( { id : id }, function( response ) {
    // various manipulation here with the response...
   defer.resolve( new Basket( ... ) );
  });
  return defer.promise;
};

Note we are using the get method of $resource.

The major issue here is that until we have the basket, we can't query the customer (because customer ID comes in the basket object).

We are querying first the basket with the basket ID. Once we have it, we can get the customer ID from the basket object and then, retrieve the customer object.

I just don't like the then pyramid. I'm not sure if we are missing something or in this case, there is no other way to do it.

Is there a cleaner way to accomplish the same thing?

\$\endgroup\$
  • \$\begingroup\$ I can't quite tell from the question itself, so to clarify: This code works, but either looks ugly or doesn't work as well as you'd like it to. \$\endgroup\$ – Nic Hartley Jun 12 '15 at 14:31
  • \$\begingroup\$ I know this code works, its just Im not quite satisfied about having two nested promises. I think is something that it must be avoided. An anti-pattern. Or maybe its not. I just want to know if this is the proper way or not. Is there any way that allow me not to nest this promises ? \$\endgroup\$ – avcajaraville Jun 12 '15 at 14:48
2
\$\begingroup\$

You shouldn't use $q.defer unless you have to.

Here you can just return the promise:

Basket.fromApi = function (id) {
    return boxesResource.get({
        id: id
    }, function (response) {
        // various manipulation here with the response...
        return new Basket(...));
    });
};

You could really clean up the resolve by creating a new service:

State

.state('catalogue', {
    resolve: {
        boxCreateData: function($stateParams, boxCreateService) {
             return boxCreateService.getBoxCreateData($stateParams.id);
        }
    }
})

Service

app.service('boxCreateService', function(Basket, Customer) {
    this.getBoxCreateData = function(id) {
        return Basket.fromApi(id)
                .then(function (basket) {
                    return Customer.fromApi(basket.getCustomer())
                        .then(function (customer) {
                            return {
                                basket: basket,
                                customer: customer
                            };
                    });
                });
    };
});

There's not much you can do to avoid two nested promises here. You need the customer ID before you can get the customer.

More on the deferred antipattern.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.