Skip to content

Commit 6f8327c

Browse files
authored
Support ParseFile upload cancel (#948)
* Support ParseFile upload cancel Fixes: #395 * Add abort status * Improve coverage * weapp abort state change
1 parent cd76082 commit 6f8327c

File tree

5 files changed

+92
-10
lines changed

5 files changed

+92
-10
lines changed

src/ParseFile.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ class ParseFile {
7070
_source: FileSource;
7171
_previousSave: ?Promise<ParseFile>;
7272
_data: ?string;
73+
_requestTask: ?any;
7374

7475
/**
7576
* @param name {String} The file's name. This will be prefixed by a unique
@@ -210,13 +211,16 @@ class ParseFile {
210211
*/
211212
save(options?: FullOptions) {
212213
options = options || {};
214+
options.requestTask = (task) => this._requestTask = task;
215+
213216
const controller = CoreManager.getFileController();
214217
if (!this._previousSave) {
215218
if (this._source.format === 'file') {
216219
this._previousSave = controller.saveFile(this._name, this._source, options).then((res) => {
217220
this._name = res.name;
218221
this._url = res.url;
219222
this._data = null;
223+
this._requestTask = null;
220224
return this;
221225
});
222226
} else if (this._source.format === 'uri') {
@@ -231,12 +235,14 @@ class ParseFile {
231235
}).then((res) => {
232236
this._name = res.name;
233237
this._url = res.url;
238+
this._requestTask = null;
234239
return this;
235240
});
236241
} else {
237242
this._previousSave = controller.saveBase64(this._name, this._source, options).then((res) => {
238243
this._name = res.name;
239244
this._url = res.url;
245+
this._requestTask = null;
240246
return this;
241247
});
242248
}
@@ -246,6 +252,13 @@ class ParseFile {
246252
}
247253
}
248254

255+
cancel() {
256+
if (this._requestTask && typeof this._requestTask.abort === 'function') {
257+
this._requestTask.abort();
258+
}
259+
this._requestTask = null;
260+
}
261+
249262
toJSON(): { name: ?string, url: ?string } {
250263
return {
251264
__type: 'File',

src/RESTController.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ if (typeof XDomainRequest !== 'undefined' &&
5151
function ajaxIE9(method: string, url: string, data: any, options?: FullOptions) {
5252
return new Promise((resolve, reject) => {
5353
const xdr = new XDomainRequest();
54+
if (options && typeof options.requestTask === 'function') {
55+
options.requestTask(xdr);
56+
}
5457
xdr.onload = function() {
5558
let response;
5659
try {
@@ -101,7 +104,12 @@ const RESTController = {
101104
);
102105
}
103106
let handled = false;
107+
let aborted = false;
108+
104109
const xhr = new XHR();
110+
if (options && typeof options.requestTask === 'function') {
111+
options.requestTask(xhr);
112+
}
105113
xhr.onreadystatechange = function() {
106114
if (xhr.readyState !== 4 || handled) {
107115
return;
@@ -124,6 +132,8 @@ const RESTController = {
124132
if (response) {
125133
promise.resolve({ response, status: xhr.status, xhr });
126134
}
135+
} else if (aborted && xhr.status === 0) {
136+
promise.resolve({ response: {}, status: 0, xhr });
127137
} else if (xhr.status >= 500 || xhr.status === 0) { // retry on 5XX or node-xmlhttprequest error
128138
if (++attempts < CoreManager.get('REQUEST_ATTEMPT_LIMIT')) {
129139
// Exponentially-growing random delay
@@ -173,7 +183,9 @@ const RESTController = {
173183
});
174184
}
175185
}
176-
186+
xhr.onabort = () => {
187+
aborted = true;
188+
};
177189
xhr.open(method, url, true);
178190

179191
for (const h in headers) {

src/Xhr.weapp.js

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ module.exports = class XhrWeapp {
99
this.responseHeader = {};
1010
this.method = '';
1111
this.url = '';
12-
this.onerror = () => {}
13-
this.onreadystatechange = () => {}
12+
this.onabort = () => {};
13+
this.onerror = () => {};
14+
this.onreadystatechange = () => {};
15+
this.requestTask = null;
1416
}
1517

1618
getAllResponseHeaders() {
@@ -34,8 +36,18 @@ module.exports = class XhrWeapp {
3436
this.url = url;
3537
}
3638

39+
abort() {
40+
if (!this.requestTask) {
41+
return;
42+
}
43+
this.requestTask.abort();
44+
this.status = 0;
45+
this.onabort();
46+
this.onreadystatechange();
47+
}
48+
3749
send(data) {
38-
wx.request({
50+
this.requestTask = wx.request({
3951
url: this.url,
4052
method: this.method,
4153
data: data,
@@ -46,10 +58,11 @@ module.exports = class XhrWeapp {
4658
this.response = res.data;
4759
this.responseHeader = res.header;
4860
this.responseText = JSON.stringify(res.data);
49-
61+
this.requestTask = null;
5062
this.onreadystatechange();
5163
},
5264
fail: (err) => {
65+
this.requestTask = null;
5366
this.onerror(err);
5467
}
5568
})

src/__tests__/ParseFile-test.js

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,31 @@ describe('ParseFile', () => {
233233
expect(f.url()).toBe('http://files.parsetfss.com/a/progress.txt');
234234
});
235235
});
236+
237+
it('can cancel file upload', () => {
238+
const mockRequestTask = {
239+
abort: () => {},
240+
};
241+
CoreManager.setFileController({
242+
saveFile: function(name, payload, options) {
243+
options.requestTask(mockRequestTask);
244+
return Promise.resolve({});
245+
},
246+
saveBase64: () => {},
247+
download: () => {},
248+
});
249+
const file = new ParseFile('progress.txt', new File(["Parse"], "progress.txt"));
250+
251+
jest.spyOn(mockRequestTask, 'abort');
252+
file.cancel();
253+
expect(mockRequestTask.abort).toHaveBeenCalledTimes(0);
254+
255+
file.save();
256+
257+
expect(file._requestTask).toEqual(mockRequestTask);
258+
file.cancel();
259+
expect(mockRequestTask.abort).toHaveBeenCalledTimes(1);
260+
});
236261
});
237262

238263
describe('FileController', () => {
@@ -294,11 +319,10 @@ describe('FileController', () => {
294319
await file.save();
295320
expect(defaultController.download).toHaveBeenCalledTimes(1);
296321
expect(defaultController.saveBase64).toHaveBeenCalledTimes(1);
297-
expect(defaultController.saveBase64.mock.calls[0]).toEqual([
298-
'parse.png',
299-
{ format: 'base64', base64: 'ParseA==', type: 'image/png' },
300-
{}
301-
]);
322+
expect(defaultController.saveBase64.mock.calls[0][0]).toEqual('parse.png');
323+
expect(defaultController.saveBase64.mock.calls[0][1]).toEqual({
324+
format: 'base64', base64: 'ParseA==', type: 'image/png'
325+
});
302326
spy.mockRestore();
303327
});
304328

src/__tests__/RESTController-test.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,26 @@ describe('RESTController', () => {
252252
expect(response.result).toBe('hello');
253253
});
254254

255+
it('handles aborted requests', (done) => {
256+
const XHR = function() { };
257+
XHR.prototype = {
258+
open: function() { },
259+
setRequestHeader: function() { },
260+
send: function() {
261+
this.status = 0;
262+
this.responseText = '{"foo":"bar"}';
263+
this.readyState = 4;
264+
this.onabort();
265+
this.onreadystatechange();
266+
}
267+
};
268+
RESTController._setXHR(XHR);
269+
RESTController.request('GET', 'classes/MyObject', {}, {})
270+
.then(() => {
271+
done();
272+
});
273+
});
274+
255275
it('attaches the session token of the current user', async () => {
256276
CoreManager.setUserController({
257277
currentUserAsync() {

0 commit comments

Comments
 (0)