Skip to content
This repository was archived by the owner on Feb 22, 2018. It is now read-only.

Commit 04a682b

Browse files
jbdeboerchirayuk
authored andcommitted
Revert "Revert "feat(http): support coalescing http requests""
This reverts commit e5859a5. Closes #1216
1 parent d97788c commit 04a682b

File tree

5 files changed

+123
-34
lines changed

5 files changed

+123
-34
lines changed

lib/core/module.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export "package:angular/core_dom/module_internal.dart" show
4343
EventHandler,
4444
Http,
4545
HttpBackend,
46+
HttpConfig,
4647
HttpDefaultHeaders,
4748
HttpDefaults,
4849
HttpInterceptor,

lib/core_dom/http.dart

Lines changed: 87 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ typedef RequestInterceptor(HttpResponseConfig);
3838
typedef RequestErrorInterceptor(dynamic);
3939
typedef Response(HttpResponse);
4040
typedef ResponseError(dynamic);
41+
typedef _CompleteResponse(HttpResponse);
42+
typedef _RunCoaleced(fn());
43+
44+
_runNow(fn()) => fn();
45+
_identity(x) => x;
4146

4247
/**
4348
* HttpInterceptors are used to modify the Http request. They can be added to
@@ -369,23 +374,29 @@ class HttpDefaults {
369374
*/
370375
@Injectable()
371376
class Http {
372-
var _pendingRequests = new HashMap<String, async.Future<HttpResponse>>();
373-
BrowserCookies _cookies;
374-
LocationWrapper _location;
375-
UrlRewriter _rewriter;
376-
HttpBackend _backend;
377-
HttpInterceptors _interceptors;
377+
final _pendingRequests = new HashMap<String, async.Future<HttpResponse>>();
378+
final BrowserCookies _cookies;
379+
final LocationWrapper _location;
380+
final UrlRewriter _rewriter;
381+
final HttpBackend _backend;
382+
final HttpInterceptors _interceptors;
383+
final RootScope _rootScope;
384+
final HttpConfig _httpConfig;
385+
final VmTurnZone _zone;
386+
387+
final _responseQueue = <Function>[];
388+
async.Timer _responseQueueTimer;
378389

379390
/**
380391
* The defaults for [Http]
381392
*/
382-
HttpDefaults defaults;
393+
final HttpDefaults defaults;
383394

384395
/**
385396
* Constructor, useful for DI.
386397
*/
387-
Http(this._cookies, this._location, this._rewriter, this._backend,
388-
this.defaults, this._interceptors);
398+
Http(this._cookies, this._location, this._rewriter, this._backend, this.defaults,
399+
this._interceptors, this._rootScope, this._httpConfig, this._zone);
389400

390401
/**
391402
* Parse a [requestUrl] and determine whether this is a same-origin request as
@@ -481,29 +492,25 @@ class Http {
481492
return new async.Future.value(new HttpResponse.copy(cachedResponse));
482493
}
483494

484-
var result = _backend.request(url,
485-
method: method,
486-
requestHeaders: config.headers,
487-
sendData: config.data,
488-
withCredentials: withCredentials).then((dom.HttpRequest value) {
489-
// TODO: Uncomment after apps migrate off of this class.
490-
// assert(value.status >= 200 && value.status < 300);
491-
492-
var response = new HttpResponse(value.status, value.responseText,
493-
parseHeaders(value), config);
494-
495-
if (cache != null) cache.put(url, response);
496-
_pendingRequests.remove(url);
497-
return response;
498-
}, onError: (error) {
499-
if (error is! dom.ProgressEvent) throw error;
500-
dom.ProgressEvent event = error;
501-
_pendingRequests.remove(url);
502-
dom.HttpRequest request = event.currentTarget;
503-
return new async.Future.error(
504-
new HttpResponse(request.status, request.response, parseHeaders(request), config));
505-
});
506-
return _pendingRequests[url] = result;
495+
requestFromBackend(runCoalesced, onComplete, onError) => _backend.request(
496+
url,
497+
method: method,
498+
requestHeaders: config.headers,
499+
sendData: config.data,
500+
withCredentials: withCredentials
501+
).then((dom.HttpRequest req) => _onResponse(req, runCoalesced, onComplete, config, cache, url),
502+
onError: (e) => _onError(e, runCoalesced, onError, config, url));
503+
504+
async.Future responseFuture;
505+
if (_httpConfig.coalesceDuration != null) {
506+
async.Completer completer = new async.Completer();
507+
responseFuture = completer.future;
508+
_zone.runOutsideAngular(() => requestFromBackend(
509+
_coalesce, completer.complete, completer.completeError));
510+
} else {
511+
responseFuture = requestFromBackend(_runNow, _identity, _identity);
512+
}
513+
return _pendingRequests[url] = responseFuture;
507514
};
508515

509516
var chain = [[serverRequest, null]];
@@ -650,11 +657,50 @@ class Http {
650657
xsrfCookieName: xsrfCookieName, interceptors: interceptors, cache: cache,
651658
timeout: timeout);
652659

660+
_onResponse(dom.HttpRequest request, _RunCoaleced runCoalesced, _CompleteResponse onComplete,
661+
HttpResponseConfig config, cache, String url) {
662+
// TODO: Uncomment after apps migrate off of this class.
663+
// assert(request.status >= 200 && request.status < 300);
664+
665+
var response = new HttpResponse(
666+
request.status, request.responseText, parseHeaders(request), config);
667+
668+
if (cache != null) cache.put(url, response);
669+
_pendingRequests.remove(url);
670+
return runCoalesced(() => onComplete(response));
671+
}
672+
673+
_onError(error, _RunCoaleced runCoalesced, _CompleteResponse onError,
674+
HttpResponseConfig config, String url) {
675+
if (error is! dom.ProgressEvent) throw error;
676+
dom.ProgressEvent event = error;
677+
_pendingRequests.remove(url);
678+
dom.HttpRequest request = event.currentTarget;
679+
var response = new HttpResponse(
680+
request.status, request.response, parseHeaders(request), config);
681+
return runCoalesced(() => onError(new async.Future.error(response)));
682+
}
683+
684+
_coalesce(fn()) {
685+
_responseQueue.add(fn);
686+
if (_responseQueueTimer == null) {
687+
_responseQueueTimer = new async.Timer(_httpConfig.coalesceDuration, _flushResponseQueue);
688+
}
689+
}
690+
691+
_flushResponseQueue() => _zone.run(_flushResponseQueueSync);
692+
693+
_flushResponseQueueSync() {
694+
_responseQueueTimer = null;
695+
_responseQueue.forEach(_runNow);
696+
_responseQueue.clear();
697+
}
698+
653699
/**
654700
* Parse raw headers into key-value object
655701
*/
656-
static Map<String, String> parseHeaders(dom.HttpRequest value) {
657-
var headers = value.getAllResponseHeaders();
702+
static Map<String, String> parseHeaders(dom.HttpRequest request) {
703+
var headers = request.getAllResponseHeaders();
658704

659705
var parsed = new HashMap();
660706

@@ -704,3 +750,10 @@ class Http {
704750
.replaceAll('%2C', ',')
705751
.replaceAll('%20', pctEncodeSpaces ? '%20' : '+');
706752
}
753+
754+
@Injectable()
755+
class HttpConfig {
756+
final Duration coalesceDuration;
757+
758+
HttpConfig({this.coalesceDuration});
759+
}

lib/core_dom/module_internal.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ class CoreDomModule extends Module {
8686
bind(HttpDefaultHeaders);
8787
bind(HttpDefaults);
8888
bind(HttpInterceptors);
89+
bind(HttpConfig, toValue: new HttpConfig());
8990
bind(Animate);
9091
bind(ViewCache);
9192
bind(BrowserCookies);

test/angular_spec.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ main() {
127127
"angular.core.dom_internal.EventHandler",
128128
"angular.core.dom_internal.Http",
129129
"angular.core.dom_internal.HttpBackend",
130+
"angular.core.dom_internal.HttpConfig",
130131
"angular.core.dom_internal.HttpDefaultHeaders",
131132
"angular.core.dom_internal.HttpDefaults",
132133
"angular.core.dom_internal.HttpInterceptor",

test/core_dom/http_spec.dart

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1407,6 +1407,39 @@ void main() {
14071407
});
14081408
});
14091409
});
1410+
1411+
describe('coalesce', () {
1412+
beforeEachModule((Module module) {
1413+
var coalesceDuration = new Duration(milliseconds: 100);
1414+
module.bind(HttpConfig, toValue: new HttpConfig(coalesceDuration: coalesceDuration));
1415+
});
1416+
1417+
it('should coalesce requests', async((Http http) {
1418+
backend.expect('GET', '/foo').respond(200, 'foo');
1419+
backend.expect('GET', '/bar').respond(200, 'bar');
1420+
1421+
var fooResp, barResp;
1422+
http.get('/foo').then((HttpResponse resp) => fooResp = resp.data);
1423+
http.get('/bar').then((HttpResponse resp) => barResp = resp.data);
1424+
1425+
microLeap();
1426+
backend.flush();
1427+
microLeap();
1428+
expect(fooResp).toBeNull();
1429+
expect(barResp).toBeNull();
1430+
1431+
clockTick(milliseconds: 99);
1432+
microLeap();
1433+
expect(fooResp).toBeNull();
1434+
expect(barResp).toBeNull();
1435+
1436+
clockTick(milliseconds: 1);
1437+
microLeap();
1438+
expect(fooResp).toEqual('foo');
1439+
expect(barResp).toEqual('bar');
1440+
}));
1441+
1442+
});
14101443
});
14111444
}
14121445

0 commit comments

Comments
 (0)