lcaaroe
lcaaroe

Reputation: 85

$q.all slower than sequential .then()?

I have some angular code that calls two separate backend services via $http.get. The backend is ASP.NET MVC 5.

I call the services via $http.get, and since I need a response from both services before continuing, I wrap the returned promises in $q.all. However, there seems be a massive overhead when resolving the promises via $q.all compared to resolving the promises sequentially (i.e. call the second service in the .then callback of the first promise).

The overhead appears in the TTFB (Time to first byte).

I can't figure out why $q.all would be slower than sequentially waiting for one promise to resolve before starting the next. In fact, I thought $q.all would be faster since it would allow me to start the second service call before the first has resolved.

Read on for implementation details.

These backend services are fairly lightweight:

ProductsController:

[HttpGet]
public Dictionary<string, PriceListTypeDto> GetPriceListTypesForProducts([FromUri] List<string> productErpIds)
{
    // Work to get PriceListTypes. Work takes 40 ms on avg.
}

UserController:

[HttpGet]
public int? GetUserOrganizationId()
{
    // work to get orgId. 1-10 ms runtime on avg.
}

Javascript functions that call these services:

var addPriceListTypes = function (replacementInfoObjects, productErpIds) {
    return productService.getPriceListTypesForProducts(productErpIds) // Returns promise from $http.get
        .then(function (response) {
            // Simple work, takes 1 ms.
        })
        .catch(function () {
        });
}


var addOrganizationSpecificDetails = function (replacementInfoObjects) {
    return userContextService.getUserOrganizationId() // Returns promise from $http.get
       .then(function (response) {
            // Simple work, takes 1 ms.
        })
        .catch(function () {
        });
};

Handling the promises:

Option 1: Takes ~600 ms before $q.all.then is called.

mapping-service.js:

var deferredResult = $q.defer();
var orgDetailsPromise = addOrganizationSpecificDetails(productInfoObjects);
var priceListPromise = addPriceListTypes(products, productErpIds);

$q.all([orgDetailsPromise, priceListPromise])
    .then(function () {
        deferredResult.resolve(productInfoObjects);
    }).catch(function () {
        deferredResult.reject();
    });

return deferredResult.promise;

Performance via Chrome devtools:

Option 2: Takes ~250 ms before both promises are resolved:

mapping-service.js:

var deferredResult = $q.defer();
addOrganizationSpecificDetails(productInfoObjects)
    .then(function () {
        addPriceListTypes(productInfoObjects, productErpIds)
            .then(function () {
                deferredResult.resolve(productInfoObjects);
            })
            .catch(function () {
                deferredResult.reject();
            });
    })
    .catch(function () {
        deferredResult.reject();
    });

return deferredResult.promise;

Performance via Chrome devtools:

Where does the overhead in option 1 come from? What have I missed? I'm completely stumped here. Please let me know if you need more information.

Upvotes: 5

Views: 723

Answers (1)

Vladimir Zdenek
Vladimir Zdenek

Reputation: 2290

I had a very similar problem when building a custom screen for Microsoft CRM a while back. I was using $q.all() and realized that by hitting the server with multiple requests at the same time, some of them failed or took really long to get resolved. Eventually we did the same thing you did - chain the requests rather than firing them all at once.

I believe this might be a similar issue to the one we had. We I am trying to say is that I am not very surprised that this is the case. I am not sure what our problem was exactly (meaning what was causing it), but it was there and out of our hands (it was the online CRM, not hosted).

I know my answer does not really offer any solution, but I though it is an insight which might give you some peace of mind.

Upvotes: 0

Related Questions