Skip to content

Commit 892636c

Browse files
authored
Support Parse.File getData cancel (#951)
* Support Parse.File getData cancel Allow to cancel downloading file data or uploading via uri (uri is first downloaded to base64 then uploaded to the server) * clean up * Documentation * Update Xhr.weapp.js * Fix tests * remove fit
1 parent dc26b15 commit 892636c

File tree

5 files changed

+133
-16
lines changed

5 files changed

+133
-16
lines changed

integration/test/ParseFileTest.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,16 @@ describe('Parse.File', () => {
3939
assert.equal(file2.url(), result.get('file2').url());
4040
});
4141

42+
it('can cancel save file with uri', async () => {
43+
const parseLogo = 'https://raw.githubusercontent.com/parse-community/parse-server/master/.github/parse-server-logo.png';
44+
const file = new Parse.File('parse-server-logo', { uri: parseLogo });
45+
file.save().then(() => {
46+
assert.equal(file.name(), undefined);
47+
assert.equal(file.url(), undefined);
48+
});
49+
file.cancel();
50+
});
51+
4252
it('can not get data from unsaved file', async () => {
4353
const file = new Parse.File('parse-server-logo', [61, 170, 236, 120]);
4454
file._data = null;

src/ParseFile.js

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,11 @@ class ParseFile {
166166
if (!this._url) {
167167
throw new Error('Cannot retrieve data for unsaved ParseFile.');
168168
}
169+
const options = {
170+
requestTask: (task) => this._requestTask = task,
171+
};
169172
const controller = CoreManager.getFileController();
170-
const result = await controller.download(this._url);
173+
const result = await controller.download(this._url, options);
171174
this._data = result.base64;
172175
return this._data;
173176
}
@@ -224,13 +227,17 @@ class ParseFile {
224227
return this;
225228
});
226229
} else if (this._source.format === 'uri') {
227-
this._previousSave = controller.download(this._source.uri).then((result) => {
230+
this._previousSave = controller.download(this._source.uri, options).then((result) => {
231+
if (!(result && result.base64)) {
232+
return {};
233+
}
228234
const newSource = {
229235
format: 'base64',
230236
base64: result.base64,
231237
type: result.contentType,
232238
};
233239
this._data = result.base64;
240+
this._requestTask = null;
234241
return controller.saveBase64(this._name, newSource, options);
235242
}).then((res) => {
236243
this._name = res.name;
@@ -252,6 +259,9 @@ class ParseFile {
252259
}
253260
}
254261

262+
/**
263+
* Aborts the request if it has already been sent.
264+
*/
255265
cancel() {
256266
if (this._requestTask && typeof this._requestTask.abort === 'function') {
257267
this._requestTask.abort();
@@ -348,15 +358,15 @@ const DefaultController = {
348358
return CoreManager.getRESTController().request('POST', path, data, options);
349359
},
350360

351-
download: function(uri) {
361+
download: function(uri, options) {
352362
if (XHR) {
353-
return this.downloadAjax(uri);
363+
return this.downloadAjax(uri, options);
354364
} else if (process.env.PARSE_BUILD === 'node') {
355365
return new Promise((resolve, reject) => {
356366
const client = uri.indexOf('https') === 0
357367
? require('https')
358368
: require('http');
359-
client.get(uri, (resp) => {
369+
const req = client.get(uri, (resp) => {
360370
resp.setEncoding('base64');
361371
let base64 = '';
362372
resp.on('data', (data) => base64 += data);
@@ -366,29 +376,38 @@ const DefaultController = {
366376
contentType: resp.headers['content-type'],
367377
});
368378
});
369-
}).on('error', reject);
379+
});
380+
req.on('abort', () => {
381+
resolve({});
382+
});
383+
req.on('error', reject);
384+
options.requestTask(req);
370385
});
371386
} else {
372387
return Promise.reject('Cannot make a request: No definition of XMLHttpRequest was found.');
373388
}
374389
},
375390

376-
downloadAjax: function(uri) {
391+
downloadAjax: function(uri, options) {
377392
return new Promise((resolve, reject) => {
378393
const xhr = new XHR();
379394
xhr.open('GET', uri, true);
380395
xhr.responseType = 'arraybuffer';
381396
xhr.onerror = function(e) { reject(e); };
382397
xhr.onreadystatechange = function() {
383-
if (xhr.readyState !== 4) {
398+
if (xhr.readyState !== xhr.DONE) {
384399
return;
385400
}
401+
if (!this.response) {
402+
return resolve({});
403+
}
386404
const bytes = new Uint8Array(this.response);
387405
resolve({
388406
base64: ParseFile.encodeBase64(bytes),
389407
contentType: xhr.getResponseHeader('content-type'),
390408
});
391409
};
410+
options.requestTask(xhr);
392411
xhr.send();
393412
});
394413
},

src/Xhr.weapp.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
module.exports = class XhrWeapp {
22
constructor() {
3+
this.UNSENT = 0;
4+
this.OPENED = 1;
5+
this.HEADERS_RECEIVED = 2;
6+
this.LOADING = 3;
7+
this.DONE = 4;
8+
39
this.header = {};
4-
this.readyState = 4;
10+
this.readyState = this.DONE;
511
this.status = 0;
612
this.response = '';
713
this.responseType = '';
@@ -42,6 +48,7 @@ module.exports = class XhrWeapp {
4248
}
4349
this.requestTask.abort();
4450
this.status = 0;
51+
this.response = undefined;
4552
this.onabort();
4653
this.onreadystatechange();
4754
}

src/__tests__/ParseConfig-test.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,6 @@ describe('ParseConfig', () => {
130130
it('can save a config object that be retrieved with masterkey only', async () => {
131131
CoreManager.setRESTController({
132132
request(method, path, body, options) {
133-
console.log(method, path, body, options);
134133
if (method === 'PUT') {
135134
expect(method).toBe('PUT');
136135
expect(path).toBe('config');

src/__tests__/ParseFile-test.js

Lines changed: 88 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ describe('FileController', () => {
315315
});
316316
});
317317

318-
jest.spyOn(defaultController, 'saveBase64');
318+
const spy2 = jest.spyOn(defaultController, 'saveBase64');
319319
await file.save();
320320
expect(defaultController.download).toHaveBeenCalledTimes(1);
321321
expect(defaultController.saveBase64).toHaveBeenCalledTimes(1);
@@ -324,6 +324,25 @@ describe('FileController', () => {
324324
format: 'base64', base64: 'ParseA==', type: 'image/png'
325325
});
326326
spy.mockRestore();
327+
spy2.mockRestore();
328+
});
329+
330+
it('save with uri download abort', async () => {
331+
const file = new ParseFile('parse.png', { uri: 'https://example.com/image.png' });
332+
const spy = jest.spyOn(
333+
defaultController,
334+
'download'
335+
)
336+
.mockImplementationOnce(() => {
337+
return Promise.resolve({});
338+
});
339+
340+
const spy2 = jest.spyOn(defaultController, 'saveBase64');
341+
await file.save();
342+
expect(defaultController.download).toHaveBeenCalledTimes(1);
343+
expect(defaultController.saveBase64).toHaveBeenCalledTimes(0);
344+
spy.mockRestore();
345+
spy2.mockRestore();
327346
});
328347

329348
it('download with base64 http', async () => {
@@ -352,6 +371,32 @@ describe('FileController', () => {
352371
spy.mockRestore();
353372
});
354373

374+
it('download with base64 http abort', async () => {
375+
defaultController._setXHR(null);
376+
const mockRequest = Object.create(EventEmitter.prototype);
377+
const mockResponse = Object.create(EventEmitter.prototype);
378+
EventEmitter.call(mockRequest);
379+
EventEmitter.call(mockResponse);
380+
mockResponse.setEncoding = function() {}
381+
mockResponse.headers = {
382+
'content-type': 'image/png'
383+
};
384+
const spy = jest.spyOn(mockHttp, 'get')
385+
.mockImplementationOnce((uri, cb) => {
386+
cb(mockResponse);
387+
return mockRequest;
388+
});
389+
const options = {
390+
requestTask: () => {},
391+
};
392+
defaultController.download('http://example.com/image.png', options).then((data) => {
393+
expect(data).toEqual({});
394+
});
395+
mockRequest.emit('aborted');
396+
mockResponse.emit('end');
397+
spy.mockRestore();
398+
});
399+
355400
it('download with base64 https', async () => {
356401
defaultController._setXHR(null);
357402
const mockResponse = Object.create(EventEmitter.prototype);
@@ -381,6 +426,7 @@ describe('FileController', () => {
381426
it('download with ajax', async () => {
382427
const mockXHR = function () {
383428
return {
429+
DONE: 4,
384430
open: jest.fn(),
385431
send: jest.fn().mockImplementation(function() {
386432
this.response = [61, 170, 236, 120];
@@ -395,12 +441,45 @@ describe('FileController', () => {
395441
};
396442
};
397443
defaultController._setXHR(mockXHR);
398-
399-
const data = await defaultController.download('https://example.com/image.png');
444+
const options = {
445+
requestTask: () => {},
446+
};
447+
const data = await defaultController.download('https://example.com/image.png', options);
400448
expect(data.base64).toBe('ParseA==');
401449
expect(data.contentType).toBe('image/png');
402450
});
403451

452+
it('download with ajax abort', async () => {
453+
const mockXHR = function () {
454+
return {
455+
open: jest.fn(),
456+
send: jest.fn().mockImplementation(function() {
457+
this.response = [61, 170, 236, 120];
458+
this.readyState = 2;
459+
this.onreadystatechange();
460+
}),
461+
getResponseHeader: function() {
462+
return 'image/png';
463+
},
464+
abort: function() {
465+
this.status = 0;
466+
this.response = undefined;
467+
this.readyState = 4;
468+
this.onreadystatechange()
469+
}
470+
};
471+
};
472+
defaultController._setXHR(mockXHR);
473+
let _requestTask;
474+
const options = {
475+
requestTask: (task) => _requestTask = task,
476+
};
477+
defaultController.download('https://example.com/image.png', options).then((data) => {
478+
expect(data).toEqual({});
479+
});
480+
_requestTask.abort();
481+
});
482+
404483
it('download with ajax error', async () => {
405484
const mockXHR = function () {
406485
return {
@@ -411,9 +490,11 @@ describe('FileController', () => {
411490
};
412491
};
413492
defaultController._setXHR(mockXHR);
414-
493+
const options = {
494+
requestTask: () => {},
495+
};
415496
try {
416-
await defaultController.download('https://example.com/image.png');
497+
await defaultController.download('https://example.com/image.png', options);
417498
} catch (e) {
418499
expect(e).toBe('error thrown');
419500
}
@@ -453,7 +534,8 @@ describe('FileController', () => {
453534
defaultController,
454535
'download'
455536
)
456-
.mockImplementationOnce(() => {
537+
.mockImplementationOnce((uri, options) => {
538+
options.requestTask(null);
457539
return Promise.resolve({
458540
base64: 'ParseA==',
459541
contentType: 'image/png',

0 commit comments

Comments
 (0)