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

Commit bf88c34

Browse files
JiaLiPassionmhevery
authored andcommitted
feat(fetch): schedule macroTask when fetch (#1075)
1 parent ff3d545 commit bf88c34

11 files changed

+366
-94
lines changed

Diff for: file-size-limit.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
{
44
"path": "dist/zone.min.js",
55
"checkTarget": true,
6-
"limit": 40144
6+
"limit": 41500
77
}
88
]
9-
}
9+
}

Diff for: lib/browser/browser.ts

+29-17
Original file line numberDiff line numberDiff line change
@@ -199,8 +199,16 @@ Zone.__load_patch('XHR', (global: any, Zone: ZoneType) => {
199199
});
200200

201201
const XMLHTTPREQUEST_SOURCE = 'XMLHttpRequest.send';
202-
const sendNative =
202+
const fetchTaskAborting = zoneSymbol('fetchTaskAborting');
203+
const fetchTaskScheduling = zoneSymbol('fetchTaskScheduling');
204+
const sendNative: Function|null =
203205
patchMethod(XMLHttpRequestPrototype, 'send', () => function(self: any, args: any[]) {
206+
if ((Zone.current as any)[fetchTaskScheduling] === true) {
207+
// a fetch is scheduling, so we are using xhr to polyfill fetch
208+
// and because we already schedule macroTask for fetch, we should
209+
// not schedule a macroTask for xhr again
210+
return sendNative!.apply(self, args);
211+
}
204212
if (self[XHR_SYNC]) {
205213
// if the XHR is sync there is no task to schedule, just execute the code.
206214
return sendNative!.apply(self, args);
@@ -212,22 +220,26 @@ Zone.__load_patch('XHR', (global: any, Zone: ZoneType) => {
212220
}
213221
});
214222

215-
const abortNative = patchMethod(XMLHttpRequestPrototype, 'abort', () => function(self: any) {
216-
const task: Task = findPendingTask(self);
217-
if (task && typeof task.type == 'string') {
218-
// If the XHR has already completed, do nothing.
219-
// If the XHR has already been aborted, do nothing.
220-
// Fix #569, call abort multiple times before done will cause
221-
// macroTask task count be negative number
222-
if (task.cancelFn == null || (task.data && (<XHROptions>task.data).aborted)) {
223-
return;
224-
}
225-
task.zone.cancelTask(task);
226-
}
227-
// Otherwise, we are trying to abort an XHR which has not yet been sent, so there is no
228-
// task
229-
// to cancel. Do nothing.
230-
});
223+
const abortNative =
224+
patchMethod(XMLHttpRequestPrototype, 'abort', () => function(self: any, args: any[]) {
225+
const task: Task = findPendingTask(self);
226+
if (task && typeof task.type == 'string') {
227+
// If the XHR has already completed, do nothing.
228+
// If the XHR has already been aborted, do nothing.
229+
// Fix #569, call abort multiple times before done will cause
230+
// macroTask task count be negative number
231+
if (task.cancelFn == null || (task.data && (<XHROptions>task.data).aborted)) {
232+
return;
233+
}
234+
task.zone.cancelTask(task);
235+
} else if ((Zone.current as any)[fetchTaskAborting] === true) {
236+
// the abort is called from fetch polyfill, we need to call native abort of XHR.
237+
return abortNative!.apply(self, args);
238+
}
239+
// Otherwise, we are trying to abort an XHR which has not yet been sent, so there is no
240+
// task
241+
// to cancel. Do nothing.
242+
});
231243
}
232244
});
233245

Diff for: lib/browser/rollup-main.ts

+1
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@
88

99
import '../zone';
1010
import '../common/promise';
11+
import '../common/fetch';
1112
import '../common/to-string';
1213
import './browser';

Diff for: lib/common/fetch.ts

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
Zone.__load_patch('fetch', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
10+
const fetch = global['fetch'];
11+
const ZoneAwarePromise = global.Promise;
12+
const symbolThenPatched = api.symbol('thenPatched');
13+
const fetchTaskScheduling = api.symbol('fetchTaskScheduling');
14+
const fetchTaskAborting = api.symbol('fetchTaskAborting');
15+
if (typeof fetch !== 'function') {
16+
return;
17+
}
18+
const OriginalAbortController = global['AbortController'];
19+
const supportAbort = typeof OriginalAbortController === 'function';
20+
let abortNative: Function|null = null;
21+
if (supportAbort) {
22+
global['AbortController'] = function() {
23+
const abortController = new OriginalAbortController();
24+
const signal = abortController.signal;
25+
signal.abortController = abortController;
26+
return abortController;
27+
};
28+
abortNative = api.patchMethod(
29+
OriginalAbortController.prototype, 'abort',
30+
(delegate: Function) => (self: any, args: any) => {
31+
if (self.task) {
32+
return self.task.zone.cancelTask(self.task);
33+
}
34+
return delegate.apply(self, args);
35+
});
36+
}
37+
const placeholder = function() {};
38+
global['fetch'] = function() {
39+
const args = Array.prototype.slice.call(arguments);
40+
const options = args.length > 1 ? args[1] : null;
41+
const signal = options && options.signal;
42+
return new Promise((res, rej) => {
43+
const task = Zone.current.scheduleMacroTask(
44+
'fetch', placeholder, args,
45+
() => {
46+
let fetchPromise;
47+
let zone = Zone.current;
48+
try {
49+
(zone as any)[fetchTaskScheduling] = true;
50+
fetchPromise = fetch.apply(this, args);
51+
} catch (error) {
52+
rej(error);
53+
return;
54+
} finally {
55+
(zone as any)[fetchTaskScheduling] = false;
56+
}
57+
58+
if (!(fetchPromise instanceof ZoneAwarePromise)) {
59+
let ctor = fetchPromise.constructor;
60+
if (!ctor[symbolThenPatched]) {
61+
api.patchThen(ctor);
62+
}
63+
}
64+
fetchPromise.then(
65+
(resource: any) => {
66+
if (task.state !== 'notScheduled') {
67+
task.invoke();
68+
}
69+
res(resource);
70+
},
71+
(error: any) => {
72+
if (task.state !== 'notScheduled') {
73+
task.invoke();
74+
}
75+
rej(error);
76+
});
77+
},
78+
() => {
79+
if (!supportAbort) {
80+
rej('No AbortController supported, can not cancel fetch');
81+
return;
82+
}
83+
if (signal && signal.abortController && !signal.aborted &&
84+
typeof signal.abortController.abort === 'function' && abortNative) {
85+
try {
86+
(Zone.current as any)[fetchTaskAborting] = true;
87+
abortNative.call(signal.abortController);
88+
} finally {
89+
(Zone.current as any)[fetchTaskAborting] = false;
90+
}
91+
} else {
92+
rej('cancel fetch need a AbortController.signal');
93+
}
94+
});
95+
if (signal && signal.abortController) {
96+
signal.abortController.task = task;
97+
}
98+
});
99+
};
100+
});

Diff for: lib/common/promise.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,8 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr
458458
(Ctor as any)[symbolThenPatched] = true;
459459
}
460460

461+
api.patchThen = patchThen;
462+
461463
function zoneify(fn: Function) {
462464
return function() {
463465
let resultPromise = fn.apply(this, arguments);
@@ -475,10 +477,10 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr
475477
if (NativePromise) {
476478
patchThen(NativePromise);
477479

478-
let fetch = global['fetch'];
480+
/*let fetch = global['fetch'];
479481
if (typeof fetch == 'function') {
480482
global['fetch'] = zoneify(fetch);
481-
}
483+
}*/
482484
}
483485

484486
// This is not part of public API, but it is useful for tests, so we expose it.

Diff for: lib/zone.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@ interface _ZonePrivate {
322322
showUncaughtError: () => boolean;
323323
patchEventTarget: (global: any, apis: any[], options?: any) => boolean[];
324324
patchOnProperties: (obj: any, properties: string[]|null) => void;
325+
patchThen: (ctro: Function) => void;
325326
setNativePromise: (nativePromise: any) => void;
326327
patchMethod:
327328
(target: any, name: string,
@@ -1326,7 +1327,8 @@ const Zone: ZoneType = (function(global: any) {
13261327
patchEventTarget: () => [],
13271328
patchOnProperties: noop,
13281329
patchMethod: () => noop,
1329-
bindArguments: () => (null as any),
1330+
bindArguments: () => [],
1331+
patchThen: () => noop,
13301332
setNativePromise: (NativePromise: any) => {
13311333
// sometimes NativePromise.resolve static function
13321334
// is not ready yet, (such as core-js/es6.promise)

Diff for: test/browser-zone-setup.ts

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ if (typeof window !== 'undefined') {
1111
}
1212
import '../lib/common/to-string';
1313
import '../lib/browser/browser';
14+
import '../lib/common/fetch';
1415
import '../lib/browser/webapis-user-media';
1516
import '../lib/browser/webapis-media-query';
1617
import '../lib/testing/zone-testing';

Diff for: test/common/Promise.spec.ts

-72
Original file line numberDiff line numberDiff line change
@@ -549,76 +549,4 @@ describe(
549549
testPromiseSubClass();
550550
});
551551
});
552-
553-
describe('fetch', ifEnvSupports('fetch', function() {
554-
it('should work for text response', function(done) {
555-
testZone.run(function() {
556-
global['fetch']('/base/test/assets/sample.json').then(function(response: any) {
557-
const fetchZone = Zone.current;
558-
expect(fetchZone).toBe(testZone);
559-
560-
response.text().then(function(text: string) {
561-
expect(Zone.current).toBe(fetchZone);
562-
expect(text.trim()).toEqual('{"hello": "world"}');
563-
done();
564-
});
565-
});
566-
});
567-
});
568-
569-
it('should work for json response', function(done) {
570-
testZone.run(function() {
571-
global['fetch']('/base/test/assets/sample.json').then(function(response: any) {
572-
const fetchZone = Zone.current;
573-
expect(fetchZone).toBe(testZone);
574-
575-
response.json().then(function(obj: any) {
576-
expect(Zone.current).toBe(fetchZone);
577-
expect(obj.hello).toEqual('world');
578-
done();
579-
});
580-
});
581-
});
582-
});
583-
584-
it('should work for blob response', function(done) {
585-
testZone.run(function() {
586-
global['fetch']('/base/test/assets/sample.json').then(function(response: any) {
587-
const fetchZone = Zone.current;
588-
expect(fetchZone).toBe(testZone);
589-
590-
// Android 4.3- doesn't support response.blob()
591-
if (response.blob) {
592-
response.blob().then(function(blob: any) {
593-
expect(Zone.current).toBe(fetchZone);
594-
expect(blob instanceof Blob).toEqual(true);
595-
done();
596-
});
597-
} else {
598-
done();
599-
}
600-
});
601-
});
602-
});
603-
604-
it('should work for arrayBuffer response', function(done) {
605-
testZone.run(function() {
606-
global['fetch']('/base/test/assets/sample.json').then(function(response: any) {
607-
const fetchZone = Zone.current;
608-
expect(fetchZone).toBe(testZone);
609-
610-
// Android 4.3- doesn't support response.arrayBuffer()
611-
if (response.arrayBuffer) {
612-
response.arrayBuffer().then(function(blob: any) {
613-
expect(Zone.current).toBe(fetchZone);
614-
expect(blob instanceof ArrayBuffer).toEqual(true);
615-
done();
616-
});
617-
} else {
618-
done();
619-
}
620-
});
621-
});
622-
});
623-
}));
624552
}));

0 commit comments

Comments
 (0)