Amitava
Amitava

Reputation: 5083

Angular $http, $q: track progress

Is there a way to track progress of http requests with Angular $http and $q? I'm making $http calls from a list of urls and then using $q.all I'm returning result of all requests. I would like to track progress of each request (promise resolved) so that I can show some progress to the user. I'm thinking of emitting event when a promise gets resolved but I'm not sure where should that be.

        var d = $q.defer();
        var promises = [];
        for(var i = 0; i < urls.length; i++){
            var url = urls[i];
            var p = $http.get(url, {responseType: "arraybuffer"});
            promises.push(p);
        }
        $q.all(promises).then(function(result){
            d.resolve(result);
        }, function(rejection){
            d.reject(rejection);
        });
        return d.promise;

EDIT: OK, after a bit of fiddling, this is what I've come up with

        var d = $q.defer();
        var promises = [];
        var completedCount = 0;
        for(var i = 0; i < urls.length; i++){
            var url = urls[i];
            var p = $http.get(url, {responseType: "arraybuffer"}).then(function(respose){
              completedCount = completedCount+1;
              var progress = Math.round((completedCount/urls.length)*100);
              $rootScope.$broadcast('download.completed', {progress: progress});
              return respose;
            }, function(error){
              return error;
            });
            promises.push(p);
        }
        $q.all(promises).then(function(result){
            d.resolve(result);
        }, function(rejection){
            d.reject(rejection);
        });
        return d.promise;

Not sure if it is the right way of doing it.

Upvotes: 4

Views: 6410

Answers (2)

Esailija
Esailija

Reputation: 140220

Make a small general helper function:

function allWithProgress(promises, progress) {
    var total = promises.length;
    var now = 0;
    promises.forEach(function(p) {
        p.then(function() {
            now++;
            progress(now / total);
        });
    })
    return $q.all(promises);
}

Then use it:

var promises = urls.map(function(url) {
    return $http.get(url, {responseType: "arraybuffer"});
});

allWithProgress(promises, function(progress) {
    progress = Math.round(progress * 100);
    $rootScope.$broadcast('download.completed', {progress: progress});
}).catch(function(error) {
    console.log(error);
});

Upvotes: 4

Endless
Endless

Reputation: 37806

I see you have already edit your own code, but if you need a more overall solution, keep reading

I once made a progress solution based on all pending http request (showing a indicator that something is loading, kind of like youtube has on the top progress bar)

js:

app.controller("ProgressCtrl", function($http) {

    this.loading = function() {
        return !!$http.pendingRequests.length;
    };

});

html:

<div id="fixedTopBar" ng-controller="ProgressCtrl as Progress">
    <div id="loading" ng-if="Progress.loading()">
        loading...
    </div>
</div>

.

Hardcore

For my latest project it wasn't just enought with just request calls. I started to get into sockets, webworker, filesystem, filereader, dataChannel and any other asynchronous calls that use $q. So i start looking into how i could get all the pending promises (including $http). Turns out there wasn't any angular solution, so i kind of monkey patched the $q provider by decorating it.

app.config(function($provide) {

    $provide.decorator("$q", function($delegate) {
        // $delegate == original $q service

        var orgDefer = $delegate.defer;
        $delegate.pendingPromises = 0;

        // overide defer method 
        $delegate.defer = function() {
            $delegate.pendingPromises++; // increass
            var defer = orgDefer();

            // decreass no mather of success or faliur
            defer.promise['finally'](function() {
                $delegate.pendingPromises--;
            });

            return defer;
        }

        return $delegate
    });

});

app.controller("ProgressCtrl", function($q) {

    this.loading = function() {
        return !!$q.pendingPromises;
    };

});

This may not perhaps fit everyone needs for production but it could be useful to developers to see if there is any unresolved issues that has been left behind and never gets called

Upvotes: 6

Related Questions