Ostatnio, w jednym z projektów, musiałem wykonywać pewne zapytanie http co X sekund. Normalnie pewnie wszystko bym opakował w plugin jQuery (albo wykorzystał istniejący) i wykonywał polecenie ajax co jakiś czas.

Niestety, albo i stety, projekt oparłem na Angular a API dostępowe zrobiłem na bazie promise. Ze względu na to, że polling miał być wykonywany tylko w kilku miejscach, nie było sensu wsadzać tego do API, jak i pisać za każdym razem ręcznie polling dla konkretnej metody/strony.

Dlatego też stworzyłem coś takiego:

var app = angular.module('poller-wrapper', []);

app.factory('poller', [
    '$timeout', '$q',
    function ($timeout, $q) {
        var tasks = [];

        function Task(opts) {
            this.opts = opts;
        }

        Task.prototype = {
            start: function () {
                var deferred = $q.defer(),
                    self = this;

                (function tick() {

                    var p = self.opts.action.call(null, self.opts.params),
                        rejected = false;

                    p.then(function (data) {
                        deferred.notify(data.data);
                    }, function () {
                        rejected = true;
                        deferred.reject('Error occured');
                    });

                    self.timeoutId = $timeout(tick, self.opts.intevral);

                })();

                this.promise = deferred.promise;

                return this;
            },
            stop: function () {
                $timeout.cancel(this.timeoutId);
                this.timeoutId = null;
            }
        };

        function create(opts) {
            var task = new Task(opts);
            tasks.push(task);

            return task;
        }

        function stopAll() {
            var i = 0; len = tasks.length;
            for (; i < len; i++) {
                tasks[i].stop();
            }
        }

        function clear() {
            stopAll();
            tasks = [];
        }

        return {
            create: create,
            stopAll: stopAll,
            clear: clear
        };
    }
]);

Wykorzystanie jest dość proste, zakładamy tylko, że metoda którą będziemy wykonywać cyklicznie zwraca zmodyfikowany promise przez usługę $http:

var app = angular.module('some-app', ['poller-wrapper']);

app.provider('api', function () {
    var baseUri = 'http://localhost';

    this.baseUri = function (uri) {
        baseUri = uri || baseUri;
    };

    function buildUri(uri) {
        return baseUri + uri;
    }

    this.$get = [
        '$http',
        function ($http) {
            return {
                action: function () {
                    return $http.get(buildUri('/action'));
                }
            };
        }
    ];
});

app.controller('NameCtrl', [
    '$http', 'poller', 'api',
    function ($http, poller, api) {

        var poll = poller.create({
            intevral: 1000,
            action: api.action
        });

        poll.start();
        poll.promise.then(/*resolve*/ null,/*reject*/ null, /*notify*/ function () {
            $scope.data = data;
        });
        poll.promise.catch((/*reject*/ function () {
            alert('error');
        });

        $scope.stop = function () {
            poll.stop();
        };
    }
]);

Co o tym sądzicie? Ma to ręce i nogi? Może da się to lepiej napisać? U mnie to śmiga, może powinienem jeszcze gdzieś testy tego wrzucić, ale to chyba będzie już inny post ;)