Добавление очереди в углы $http service

У меня очень причудливый api, который может обрабатывать только один запрос за раз. Поэтому я должен гарантировать, что каждый раз, когда запрос выполняется, он переходит в очередь, и эта очередь выполняется по одному запросу за раз, пока она не будет пустой.

Обычно я просто использую jQuery, встроенный в очередь, так как сайт уже использует jQuery. Тем не менее, я не был уверен, смогу ли я каким-то образом украсить сервис $http, или обернуть его в другую службу, которая возвращает одно обещание за раз или что-то еще.

Ответы

Ответ 1

Вот мое решение для этого: http://plnkr.co/edit/Tmjw0MCfSbBSgWRhFvcg

Идея такова: каждый запуск службы добавляет запрос в очередь и возвращает обещание. Когда запрос на $http завершен, разрешите/отмените обещанное обещание и выполните следующую задачу из очереди, если таковая имеется.

app.factory('srv', function($q,$http) {

  var queue=[];
  var execNext = function() {
    var task = queue[0];
    $http(task.c).then(function(data) {
      queue.shift();
      task.d.resolve(data);
      if (queue.length>0) execNext();
    }, function(err) {
      queue.shift();
      task.d.reject(err);
      if (queue.length>0) execNext();
    })
    ;
  }; 
  return function(config) {
    var d = $q.defer();
    queue.push({c:config,d:d});
    if (queue.length===1) execNext();            
    return d.promise;
  };
});

Выглядит довольно просто:)

Ответ 2

Основываясь на большой работе Валентина, я перевернул этот код в автономный перехватчик запросов/ответов Angular (v1.2 +). Он будет автоматически запрограммировать запросы $http, не перерабатывая ваш код, чтобы использовать srv() везде:

( function() {

'use strict';

angular.module( 'app' ).config( [ '$httpProvider', function( $httpProvider ) {

    /**
     * Interceptor to queue HTTP requests.
     */

    $httpProvider.interceptors.push( [ '$q', function( $q ) {

        var _queue = [];

        /**
         * Shifts and executes the top function on the queue (if any). Note this function executes asynchronously (with a timeout of 1). This
         * gives 'response' and 'responseError' chance to return their values and have them processed by their calling 'success' or 'error'
         * methods. This is important if 'success' involves updating some timestamp on some object which the next message in the queue relies
         * upon.
         */

        function _shiftAndExecuteTop() {

            setTimeout( function() {

                _queue.shift();

                if ( _queue.length > 0 ) {
                    _queue[0]();
                }
            }, 1 );
        }

        return {

            /**
             * Blocks each request on the queue. If the first request, processes immediately.
             */

            request: function( config ) {

                var deferred = $q.defer();
                _queue.push( function() {

                    deferred.resolve( config );
                } );

                if ( _queue.length === 1 ) {
                    _queue[0]();
                }

                return deferred.promise;
            },

            /**
             * After each response completes, unblocks the next request.
             */

            response: function( response ) {

                _shiftAndExecuteTop();
                return response;
            },

            /**
             * After each response errors, unblocks the next request.
             */

            responseError: function( responseError ) {

                _shiftAndExecuteTop();
                return $q.reject( responseError );
            },
        };
    } ] );
} ] );

} )();

Ответ 3

Ричард: ваш код работает идеально, но он также работает с внутренним запросом, например template или $templateProviders.

Вот решение для работы только с внешними http запросами

/**
 * Interceptor to queue HTTP requests.
 */
$httpProvider.interceptors.push(['$q', function ($q) {
    var _queue = [];

    /**
     * Executes the top function on the queue (if any).
     */
    function _executeTop() {
        if (_queue.length === 0) {
            return;
        }
        _queue[0]();
    }

    return {
        /**
         * Blocks each request on the queue. If the first request, processes immediately.
         */
        request: function (config) {
            if (config.url.substring(0, 4) == 'http') {
                var deferred = $q.defer();
                _queue.push(function () {
                    deferred.resolve(config);
                });
                if (_queue.length === 1) {
                    _executeTop();
                }
                return deferred.promise;
            } else {
                return config;
            }
        },
        /**
         * After each response completes, unblocks the next request.
         */
        response: function (response) {
            if (response.config.url.substring(0, 4) == 'http') {
                _queue.shift();
                _executeTop();
            }
            return response;
        },
        /**
         * After each response errors, unblocks the next request.
         */
        responseError: function (responseError) {
            if (responseError.config.url.substring(0, 4) == 'http') {
                _queue.shift();
                _executeTop();
            }
            return $q.reject(responseError);
        },
    };
}]);

Ответ 4

.factory('qHttp', function($q, $http) {
  var queue = $q.when();

  return function queuedHttp(httpConf) {
    var f = function(data) {
      return $http(httpConf);
    };
    return queue = queue.then(f, f);
  };
})

Как использовать:

var apis = ['//httpbin.org/ip', '//httpbin.org/user-agent', '//httpbin.org/headers'];

for (var i = 0; i < 100; i++) {
  qHttp({
    method: 'get', 
    url: apis[i % apis.length]
  })
  .then(function(data) { 
    console.log(data.data); 
  });
}

Ответ 5

Если кому-то нужно решение для последовательных http-вызовов (как указано в OP) в Angular 5, то следующее решение:

    import { Injectable } from '@angular/core';
    import { Response } from '@angular/http';
    import { HttpClient } from '@angular/common/http';
    import { Observable } from 'rxjs/Observable';
    import { Subject } from 'rxjs/Subject'

    export class PendingRequest {
      url: string;
      method: string;
      options: any;
      subscription: Subject<any>;

      constructor(url: string, method: string, options: any, subscription: Subject<any>) {
        this.url = url;
        this.method = method;
        this.options = options;
        this.subscription = subscription;
      }
    }

    @Injectable()
    export class BackendService {
      private requests$ = new Subject<any>();
      private queue: PendingRequest[] = [];

      constructor(private httpClient: HttpClient) {
        this.requests$.subscribe(request => this.execute(request));
      }

      /** Call this method to add your http request to queue */
      invoke(url, method, params, options) {
        return this.addRequestToQueue(url, method, params, options);
      }

      private execute(requestData) {
        //One can enhance below method to fire post/put as well. (somehow .finally is not working for me)
        const req = this.httpClient.get(requestData.url)
          .subscribe(res => {
            const sub = requestData.subscription;
            sub.next(res);
            this.queue.shift();
            this.startNextRequest();
          });
      }

      private addRequestToQueue(url, method, params, options) {
        const sub = new Subject<any>();
        const request = new PendingRequest(url, method, options, sub);

        this.queue.push(request);
        if (this.queue.length === 1) {
          this.startNextRequest();
        }
        return sub;
      }

      private startNextRequest() {
        // get next request, if any.
        if (this.queue.length > 0) {
          this.execute(this.queue[0]);
        }
      }
    }

Если кто-то хочет посмотреть на рабочий плукер, тогда вот рабочий плункер.