Add support for ontimeout and onerror handler when using XMLHttpRequest for Android and iOS

Summary:Currently React-Native does not have `ontimeout` and `onerror` handlers for [XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest). This is an extension to [No timeout on XMLHttpRequest](https://github.com/facebook/react-native/issues/4648).

With addition to two handlers, both Android and iOS can now handle `ontimeout` if request times out and `onerror` when there is general network error.

**Test plan**

Code has been tested on both Android and iOS with [Charles](https://www.charlesproxy.com/) by setting a breakpoint on the request which fires `ontimeout` when the request waits beyond `timeout` time and `onerror` when there is network error.

**Usage**

JavaScript -

```
var request = new XMLHttpRequest();

function onLoad() {
    console.log(request.status);
};

function onTimeout() {
    console.log('Timeout');
};

function onError() {
    console.log('General network error');
};

request.onload = onLoad;
request.ontimeout = onTimeout;
request.onerr
Closes https://github.com/facebook/react-native/pull/6841

Differential Revision: D3178859

Pulled By: lexs

fb-gh-sync-id: 30674570653e92ab5f7e74bd925dd5640fc862b6
fbshipit-source-id: 30674570653e92ab5f7e74bd925dd5640fc862b6
This commit is contained in:
grgmo
2016-04-15 05:16:53 -07:00
committed by Facebook Github Bot 9
parent 967dbd0cbe
commit d09cd62011
8 changed files with 248 additions and 20 deletions

View File

@@ -385,6 +385,7 @@ RCT_EXPORT_MODULE()
}
NSArray *responseJSON = @[task.requestID,
RCTNullIfNil(error.localizedDescription),
error.code == kCFURLErrorTimedOut ? @YES : @NO
];
[_bridge.eventDispatcher sendDeviceEventWithName:@"didCompleteNetworkResponse"

View File

@@ -61,6 +61,8 @@ class XMLHttpRequestBase {
status: number;
timeout: number;
responseURL: ?string;
ontimeout: ?Function;
onerror: ?Function;
upload: ?{
onprogress?: (event: Object) => void;
@@ -79,6 +81,7 @@ class XMLHttpRequestBase {
_responseType: ResponseType;
_sent: boolean;
_url: ?string;
_timedOut: boolean;
constructor() {
this.UNSENT = UNSENT;
@@ -91,11 +94,15 @@ class XMLHttpRequestBase {
this.onload = null;
this.upload = undefined; /* Upload not supported yet */
this.timeout = 0;
this.ontimeout = null;
this.onerror = null;
this._reset();
this._method = null;
this._url = null;
this._aborted = false;
this._timedOut = false;
this._hasError = false;
}
_reset(): void {
@@ -115,6 +122,7 @@ class XMLHttpRequestBase {
this._lowerCaseResponseHeaders = {};
this._clearSubscriptions();
this._timedOut = false;
}
// $FlowIssue #10784535
@@ -249,11 +257,14 @@ class XMLHttpRequestBase {
}
}
_didCompleteResponse(requestId: number, error: string): void {
_didCompleteResponse(requestId: number, error: string, timeOutError: boolean): void {
if (requestId === this._requestId) {
if (error) {
this.responseText = error;
this._hasError = true;
if (timeOutError) {
this._timedOut = true;
}
}
this._clearSubscriptions();
this._requestId = null;
@@ -362,17 +373,25 @@ class XMLHttpRequestBase {
onreadystatechange.call(this, null);
}
if (newState === this.DONE && !this._aborted) {
this._sendLoad();
if (this._hasError) {
if (this._timedOut) {
this._sendEvent(this.ontimeout);
} else {
this._sendEvent(this.onerror);
}
}
else {
this._sendEvent(this.onload);
}
}
}
_sendLoad(): void {
_sendEvent(newEvent: ?Function): void {
// TODO: workaround flow bug with nullable function checks
var onload = this.onload;
if (onload) {
if (newEvent) {
// We should send an event to handler, but since we don't process that
// event anywhere, let's leave it empty
onload(null);
newEvent(null);
}
}
}

View File

@@ -0,0 +1,48 @@
'use strict';
jest
.autoMockOff()
.dontMock('XMLHttpRequestBase');
const XMLHttpRequestBase = require('XMLHttpRequestBase');
describe('XMLHttpRequestBase', function(){
var xhr;
beforeEach(() => {
xhr = new XMLHttpRequestBase();
xhr.ontimeout = jest.fn();
xhr.onerror = jest.fn();
xhr.onload = jest.fn();
xhr.didCreateRequest(1);
});
afterEach(() => {
xhr = null;
});
it('should call ontimeout function when the request times out', function(){
xhr._didCompleteResponse(1, 'Timeout', true);
expect(xhr.ontimeout).toBeCalledWith(null);
expect(xhr.onerror).not.toBeCalled();
expect(xhr.onload).not.toBeCalled();
});
it('should call onerror function when the request times out', function(){
xhr._didCompleteResponse(1, 'Generic error');
expect(xhr.onerror).toBeCalledWith(null);
expect(xhr.ontimeout).not.toBeCalled();
expect(xhr.onload).not.toBeCalled();
});
it('should call onload function when there is no error', function(){
xhr._didCompleteResponse(1, null);
expect(xhr.onload).toBeCalledWith(null);
expect(xhr.onerror).not.toBeCalled();
expect(xhr.ontimeout).not.toBeCalled();
});
});