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

Lines changed: 366 additions & 94 deletions

File tree

‎file-size-limit.json‎

Lines changed: 2 additions & 2 deletions
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+
}

‎lib/browser/browser.ts‎

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

201201
constXMLHTTPREQUEST_SOURCE='XMLHttpRequest.send';
202-
constsendNative=
202+
constfetchTaskAborting=zoneSymbol('fetchTaskAborting');
203+
constfetchTaskScheduling=zoneSymbol('fetchTaskScheduling');
204+
constsendNative: Function|null=
203205
patchMethod(XMLHttpRequestPrototype,'send',()=>function(self: any,args: any[]){
206+
if((Zone.currentasany)[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+
returnsendNative!.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
returnsendNative!.apply(self,args);
@@ -212,22 +220,26 @@ Zone.__load_patch('XHR', (global: any, Zone: ZoneType) => {
212220
}
213221
});
214222

215-
constabortNative=patchMethod(XMLHttpRequestPrototype,'abort',()=>function(self: any){
216-
consttask: Task=findPendingTask(self);
217-
if(task&&typeoftask.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+
constabortNative=
224+
patchMethod(XMLHttpRequestPrototype,'abort',()=>function(self: any,args: any[]){
225+
consttask: Task=findPendingTask(self);
226+
if(task&&typeoftask.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+
}elseif((Zone.currentasany)[fetchTaskAborting]===true){
236+
// the abort is called from fetch polyfill, we need to call native abort of XHR.
237+
returnabortNative!.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

‎lib/browser/rollup-main.ts‎

Lines changed: 1 addition & 0 deletions
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';

‎lib/common/fetch.ts‎

Lines changed: 100 additions & 0 deletions
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+
constfetch=global['fetch'];
11+
constZoneAwarePromise=global.Promise;
12+
constsymbolThenPatched=api.symbol('thenPatched');
13+
constfetchTaskScheduling=api.symbol('fetchTaskScheduling');
14+
constfetchTaskAborting=api.symbol('fetchTaskAborting');
15+
if(typeoffetch!=='function'){
16+
return;
17+
}
18+
constOriginalAbortController=global['AbortController'];
19+
constsupportAbort=typeofOriginalAbortController==='function';
20+
letabortNative: Function|null=null;
21+
if(supportAbort){
22+
global['AbortController']=function(){
23+
constabortController=newOriginalAbortController();
24+
constsignal=abortController.signal;
25+
signal.abortController=abortController;
26+
returnabortController;
27+
};
28+
abortNative=api.patchMethod(
29+
OriginalAbortController.prototype,'abort',
30+
(delegate: Function)=>(self: any,args: any)=>{
31+
if(self.task){
32+
returnself.task.zone.cancelTask(self.task);
33+
}
34+
returndelegate.apply(self,args);
35+
});
36+
}
37+
constplaceholder=function(){};
38+
global['fetch']=function(){
39+
constargs=Array.prototype.slice.call(arguments);
40+
constoptions=args.length>1 ? args[1] : null;
41+
constsignal=options&&options.signal;
42+
returnnewPromise((res,rej)=>{
43+
consttask=Zone.current.scheduleMacroTask(
44+
'fetch',placeholder,args,
45+
()=>{
46+
letfetchPromise;
47+
letzone=Zone.current;
48+
try{
49+
(zoneasany)[fetchTaskScheduling]=true;
50+
fetchPromise=fetch.apply(this,args);
51+
}catch(error){
52+
rej(error);
53+
return;
54+
}finally{
55+
(zoneasany)[fetchTaskScheduling]=false;
56+
}
57+
58+
if(!(fetchPromiseinstanceofZoneAwarePromise)){
59+
letctor=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+
typeofsignal.abortController.abort==='function'&&abortNative){
85+
try{
86+
(Zone.currentasany)[fetchTaskAborting]=true;
87+
abortNative.call(signal.abortController);
88+
}finally{
89+
(Zone.currentasany)[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+
});

‎lib/common/promise.ts‎

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

461+
api.patchThen=patchThen;
462+
461463
functionzoneify(fn: Function){
462464
returnfunction(){
463465
letresultPromise=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-
letfetch=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.

‎lib/zone.ts‎

Lines changed: 3 additions & 1 deletion
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: ()=>(nullasany),
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)

‎test/browser-zone-setup.ts‎

Lines changed: 1 addition & 0 deletions
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';

‎test/common/Promise.spec.ts‎

Lines changed: 0 additions & 72 deletions
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-
constfetchZone=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-
constfetchZone=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-
constfetchZone=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(blobinstanceofBlob).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-
constfetchZone=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(blobinstanceofArrayBuffer).toEqual(true);
615-
done();
616-
});
617-
}else{
618-
done();
619-
}
620-
});
621-
});
622-
});
623-
}));
624552
}));

0 commit comments

Comments
 (0)