我有一个非常古怪的 api,一次只能处理一个请求。因此,我需要确保每次发出请求时,它都会进入一个队列,并且该队列一次执行一个请求,直到它为空。
通常,我只使用 jQuery 的内置队列,因为该站点已经在使用 jQuery。但是,我不确定是否可以以某种方式装饰 $http 服务,或者将其包装在另一个服务中,该服务一次返回一个承诺,或者其他什么。
我有一个非常古怪的 api,一次只能处理一个请求。因此,我需要确保每次发出请求时,它都会进入一个队列,并且该队列一次执行一个请求,直到它为空。
通常,我只使用 jQuery 的内置队列,因为该站点已经在使用 jQuery。但是,我不确定是否可以以某种方式装饰 $http 服务,或者将其包装在另一个服务中,该服务一次返回一个承诺,或者其他什么。
这是我的解决方案: 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;
};
});
看起来很简单:)
在上述 Valentyn 的出色工作的基础上,我将这段代码放入了一个独立的 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 );
},
};
} ] );
} ] );
} )();
理查德:你的代码运行完美,但它也适用于内部请求,如template
or $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);
},
};
}]);
.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);
});
}
如果有人需要解决 Angular 5 中的顺序 http 调用(如 OP 所述),那么解决方案如下:
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]);
}
}
}
如果有人想查看正在工作的 plunker,那么这里就是正在工作的 plunker。
我的两个队列:
代码:
app.factory('QueueHttp', ($http, $q) => {
let promise = $q.resolve();
return (conf) => {
let next = () => {
return $http(conf);
};
return promise = promise.then(next);
};
});
用法:
return QueueHttp({
url: url,
method: 'GET'
});
代码:
app.factory('DelayHttp', ($http, $timeout) => {
let counter = 0,
delay = 100;
return (conf) => {
counter += 1;
return $timeout(() => {
counter -= 1;
return $http(conf);
}, counter * delay);
};
});
用法:
return DelayHttp({
url: url,
method: 'GET'
});