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

Commit

Permalink
feat(fetch): schedule macroTask when fetch
Browse files Browse the repository at this point in the history
  • Loading branch information
JiaLiPassion committed Jun 26, 2018
1 parent ff3d545 commit ee64c90
Show file tree
Hide file tree
Showing 11 changed files with 366 additions and 94 deletions.
4 changes: 2 additions & 2 deletions file-size-limit.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{
"path": "dist/zone.min.js",
"checkTarget": true,
"limit": 40144
"limit": 41500
}
]
}
}
46 changes: 29 additions & 17 deletions lib/browser/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,16 @@ Zone.__load_patch('XHR', (global: any, Zone: ZoneType) => {
});

const XMLHTTPREQUEST_SOURCE = 'XMLHttpRequest.send';
const sendNative =
const fetchTaskAborting = zoneSymbol('fetchTaskAborting');
const fetchTaskScheduling = zoneSymbol('fetchTaskScheduling');
const sendNative: Function|null =
patchMethod(XMLHttpRequestPrototype, 'send', () => function(self: any, args: any[]) {
if ((Zone.current as any)[fetchTaskScheduling]) {
// a fetch is scheduling, so we are using xhr to polyfill fetch
// and because we already schedule macroTask for fetch, we should
// not schedule a macroTask for xhr again
return sendNative!.apply(self, args);
}
if (self[XHR_SYNC]) {
// if the XHR is sync there is no task to schedule, just execute the code.
return sendNative!.apply(self, args);
Expand All @@ -212,22 +220,26 @@ Zone.__load_patch('XHR', (global: any, Zone: ZoneType) => {
}
});

const abortNative = patchMethod(XMLHttpRequestPrototype, 'abort', () => function(self: any) {
const task: Task = findPendingTask(self);
if (task && typeof task.type == 'string') {
// If the XHR has already completed, do nothing.
// If the XHR has already been aborted, do nothing.
// Fix #569, call abort multiple times before done will cause
// macroTask task count be negative number
if (task.cancelFn == null || (task.data && (<XHROptions>task.data).aborted)) {
return;
}
task.zone.cancelTask(task);
}
// Otherwise, we are trying to abort an XHR which has not yet been sent, so there is no
// task
// to cancel. Do nothing.
});
const abortNative =
patchMethod(XMLHttpRequestPrototype, 'abort', () => function(self: any, args: any[]) {
const task: Task = findPendingTask(self);
if (task && typeof task.type == 'string') {
// If the XHR has already completed, do nothing.
// If the XHR has already been aborted, do nothing.
// Fix #569, call abort multiple times before done will cause
// macroTask task count be negative number
if (task.cancelFn == null || (task.data && (<XHROptions>task.data).aborted)) {
return;
}
task.zone.cancelTask(task);
} else if ((Zone.current as any)[fetchTaskAborting] === true) {
// the abort is called from fetch polyfill, we need to call native abort of XHR.
return abortNative!.apply(self, args);
}
// Otherwise, we are trying to abort an XHR which has not yet been sent, so there is no
// task
// to cancel. Do nothing.
});
}
});

Expand Down
1 change: 1 addition & 0 deletions lib/browser/rollup-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@

import '../zone';
import '../common/promise';
import '../common/fetch';
import '../common/to-string';
import './browser';
100 changes: 100 additions & 0 deletions lib/common/fetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

Zone.__load_patch('fetch', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
const fetch = global['fetch'];
const ZoneAwarePromise = global.Promise;
const symbolThenPatched = api.symbol('thenPatched');
const fetchTaskScheduling = api.symbol('fetchTaskScheduling');
const fetchTaskAborting = api.symbol('fetchTaskAborting');
if (typeof fetch !== 'function') {
return;
}
const OriginalAbortController = global['AbortController'];
const supportAbort = typeof OriginalAbortController === 'function';
let abortNative: Function|null = null;
if (supportAbort) {
global['AbortController'] = function() {
const abortController = new OriginalAbortController();
const signal = abortController.signal;
signal.abortController = abortController;
return abortController;
};
abortNative = api.patchMethod(
OriginalAbortController.prototype, 'abort',
(delegate: Function) => (self: any, args: any) => {
if (self.task) {
return self.task.zone.cancelTask(self.task);
}
return delegate.apply(self, args);
});
}
const placeholder = function() {};
global['fetch'] = function() {
const args = Array.prototype.slice.call(arguments);
const options = args.length > 1 ? args[1] : null;
const signal = options && options.signal;
return new Promise((res, rej) => {
const task = Zone.current.scheduleMacroTask(
'fetch', placeholder, args,
() => {
let fetchPromise;
let zone = Zone.current;
try {
(zone as any)[fetchTaskScheduling] = true;
fetchPromise = fetch.apply(this, args);
} catch (error) {
rej(error);
return;
} finally {
(zone as any)[fetchTaskScheduling] = false;
}

if (!(fetchPromise instanceof ZoneAwarePromise)) {
let ctor = fetchPromise.constructor;
if (!ctor[symbolThenPatched]) {
api.patchThen(ctor);
}
}
fetchPromise.then(
(resource: any) => {
if (task.state !== 'notScheduled') {
task.invoke();
}
res(resource);
},
(error: any) => {
if (task.state !== 'notScheduled') {
task.invoke();
}
rej(error);
});
},
() => {
if (!supportAbort) {
rej('No AbortController supported, can not cancel fetch');
return;
}
if (signal && signal.abortController && !signal.aborted &&
typeof signal.abortController.abort === 'function' && abortNative) {
try {
(Zone.current as any)[fetchTaskAborting] = true;
abortNative.call(signal.abortController);
} finally {
(Zone.current as any)[fetchTaskAborting] = false;
}
} else {
rej('cancel fetch need a AbortController.signal');
}
});
if (signal && signal.abortController) {
signal.abortController.task = task;
}
});
};
});
6 changes: 4 additions & 2 deletions lib/common/promise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,8 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr
(Ctor as any)[symbolThenPatched] = true;
}

api.patchThen = patchThen;

function zoneify(fn: Function) {
return function() {
let resultPromise = fn.apply(this, arguments);
Expand All @@ -475,10 +477,10 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr
if (NativePromise) {
patchThen(NativePromise);

let fetch = global['fetch'];
/*let fetch = global['fetch'];
if (typeof fetch == 'function') {
global['fetch'] = zoneify(fetch);
}
}*/
}

// This is not part of public API, but it is useful for tests, so we expose it.
Expand Down
4 changes: 3 additions & 1 deletion lib/zone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ interface _ZonePrivate {
showUncaughtError: () => boolean;
patchEventTarget: (global: any, apis: any[], options?: any) => boolean[];
patchOnProperties: (obj: any, properties: string[]|null) => void;
patchThen: (ctro: Function) => void;
setNativePromise: (nativePromise: any) => void;
patchMethod:
(target: any, name: string,
Expand Down Expand Up @@ -1326,7 +1327,8 @@ const Zone: ZoneType = (function(global: any) {
patchEventTarget: () => [],
patchOnProperties: noop,
patchMethod: () => noop,
bindArguments: () => (null as any),
bindArguments: () => [],
patchThen: () => noop,
setNativePromise: (NativePromise: any) => {
// sometimes NativePromise.resolve static function
// is not ready yet, (such as core-js/es6.promise)
Expand Down
1 change: 1 addition & 0 deletions test/browser-zone-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ if (typeof window !== 'undefined') {
}
import '../lib/common/to-string';
import '../lib/browser/browser';
import '../lib/common/fetch';
import '../lib/browser/webapis-user-media';
import '../lib/browser/webapis-media-query';
import '../lib/testing/zone-testing';
Expand Down
72 changes: 0 additions & 72 deletions test/common/Promise.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -549,76 +549,4 @@ describe(
testPromiseSubClass();
});
});

describe('fetch', ifEnvSupports('fetch', function() {
it('should work for text response', function(done) {
testZone.run(function() {
global['fetch']('/base/test/assets/sample.json').then(function(response: any) {
const fetchZone = Zone.current;
expect(fetchZone).toBe(testZone);

response.text().then(function(text: string) {
expect(Zone.current).toBe(fetchZone);
expect(text.trim()).toEqual('{"hello": "world"}');
done();
});
});
});
});

it('should work for json response', function(done) {
testZone.run(function() {
global['fetch']('/base/test/assets/sample.json').then(function(response: any) {
const fetchZone = Zone.current;
expect(fetchZone).toBe(testZone);

response.json().then(function(obj: any) {
expect(Zone.current).toBe(fetchZone);
expect(obj.hello).toEqual('world');
done();
});
});
});
});

it('should work for blob response', function(done) {
testZone.run(function() {
global['fetch']('/base/test/assets/sample.json').then(function(response: any) {
const fetchZone = Zone.current;
expect(fetchZone).toBe(testZone);

// Android 4.3- doesn't support response.blob()
if (response.blob) {
response.blob().then(function(blob: any) {
expect(Zone.current).toBe(fetchZone);
expect(blob instanceof Blob).toEqual(true);
done();
});
} else {
done();
}
});
});
});

it('should work for arrayBuffer response', function(done) {
testZone.run(function() {
global['fetch']('/base/test/assets/sample.json').then(function(response: any) {
const fetchZone = Zone.current;
expect(fetchZone).toBe(testZone);

// Android 4.3- doesn't support response.arrayBuffer()
if (response.arrayBuffer) {
response.arrayBuffer().then(function(blob: any) {
expect(Zone.current).toBe(fetchZone);
expect(blob instanceof ArrayBuffer).toEqual(true);
done();
});
} else {
done();
}
});
});
});
}));
}));
Loading

0 comments on commit ee64c90

Please sign in to comment.