Skip to content

Commit cea20a6

Browse files
authored
Add batchSize to saveAll / destroyAll (#701)
* Add batchSize to saveAll * add batchSize to destroyAll
1 parent e2997cb commit cea20a6

File tree

3 files changed

+155
-2
lines changed

3 files changed

+155
-2
lines changed

src/ParseObject.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ type SaveParams = {
5353
body: AttributeMap;
5454
};
5555

56+
const DEFAULT_BATCH_SIZE = 20;
57+
5658
// Mapping of class names to constructors, so we can populate objects from the
5759
// server with appropriate subclasses of ParseObject
5860
var classMap = {};
@@ -1339,6 +1341,7 @@ class ParseObject {
13391341
* be used for this request.
13401342
* <li>sessionToken: A valid session token, used for making a request on
13411343
* behalf of a specific user.
1344+
* <li>batchSize: Number of objects to process per request
13421345
* </ul>
13431346
* @return {Promise} A promise that is fulfilled when the destroyAll
13441347
* completes.
@@ -1351,6 +1354,9 @@ class ParseObject {
13511354
if (options.hasOwnProperty('sessionToken')) {
13521355
destroyOptions.sessionToken = options.sessionToken;
13531356
}
1357+
if (options.hasOwnProperty('batchSize') && typeof options.batchSize === 'number') {
1358+
destroyOptions.batchSize = options.batchSize;
1359+
}
13541360
return CoreManager.getObjectController().destroy(
13551361
list,
13561362
destroyOptions
@@ -1378,6 +1384,7 @@ class ParseObject {
13781384
* be used for this request.
13791385
* <li>sessionToken: A valid session token, used for making a request on
13801386
* behalf of a specific user.
1387+
* <li>batchSize: Number of objects to process per request
13811388
* </ul>
13821389
*/
13831390
static saveAll(list: Array<ParseObject>, options = {}) {
@@ -1388,6 +1395,9 @@ class ParseObject {
13881395
if (options.hasOwnProperty('sessionToken')) {
13891396
saveOptions.sessionToken = options.sessionToken;
13901397
}
1398+
if (options.hasOwnProperty('batchSize') && typeof options.batchSize === 'number') {
1399+
saveOptions.batchSize = options.batchSize;
1400+
}
13911401
return CoreManager.getObjectController().save(
13921402
list,
13931403
saveOptions
@@ -1729,6 +1739,8 @@ var DefaultController = {
17291739
},
17301740

17311741
destroy(target: ParseObject | Array<ParseObject>, options: RequestOptions): Promise {
1742+
const batchSize = (options && options.batchSize) ? options.batchSize : DEFAULT_BATCH_SIZE;
1743+
17321744
var RESTController = CoreManager.getRESTController();
17331745
if (Array.isArray(target)) {
17341746
if (target.length < 1) {
@@ -1740,7 +1752,7 @@ var DefaultController = {
17401752
return;
17411753
}
17421754
batches[batches.length - 1].push(obj);
1743-
if (batches[batches.length - 1].length >= 20) {
1755+
if (batches[batches.length - 1].length >= batchSize) {
17441756
batches.push([]);
17451757
}
17461758
});
@@ -1796,6 +1808,8 @@ var DefaultController = {
17961808
},
17971809

17981810
save(target: ParseObject | Array<ParseObject | ParseFile>, options: RequestOptions) {
1811+
const batchSize = (options && options.batchSize) ? options.batchSize : DEFAULT_BATCH_SIZE;
1812+
17991813
var RESTController = CoreManager.getRESTController();
18001814
var stateController = CoreManager.getObjectStateController();
18011815
if (Array.isArray(target)) {
@@ -1831,7 +1845,7 @@ var DefaultController = {
18311845
var batch = [];
18321846
var nextPending = [];
18331847
pending.forEach((el) => {
1834-
if (batch.length < 20 && canBeSerialized(el)) {
1848+
if (batch.length < batchSize && canBeSerialized(el)) {
18351849
batch.push(el);
18361850
} else {
18371851
nextPending.push(el);

src/RESTController.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export type RequestOptions = {
1616
useMasterKey?: boolean;
1717
sessionToken?: string;
1818
installationId?: string;
19+
batchSize?: number;
1920
include?: any;
2021
};
2122

src/__tests__/ParseObject-test.js

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1531,6 +1531,69 @@ describe('ParseObject', () => {
15311531
jest.runAllTicks();
15321532
});
15331533

1534+
it('can saveAll with batchSize', async (done) => {
1535+
const xhrs = [];
1536+
for (let i = 0; i < 2; i++) {
1537+
xhrs[i] = {
1538+
setRequestHeader: jest.fn(),
1539+
open: jest.fn(),
1540+
send: jest.fn(),
1541+
status: 200,
1542+
readyState: 4
1543+
};
1544+
}
1545+
let current = 0;
1546+
RESTController._setXHR(function() { return xhrs[current++]; });
1547+
const objects = [];
1548+
for (let i = 0; i < 22; i++) {
1549+
objects[i] = new ParseObject('Person');
1550+
}
1551+
ParseObject.saveAll(objects, { batchSize: 20 }).then(() => {
1552+
expect(xhrs[0].open.mock.calls[0]).toEqual(
1553+
['POST', 'https://api.parse.com/1/batch', true]
1554+
);
1555+
expect(xhrs[1].open.mock.calls[0]).toEqual(
1556+
['POST', 'https://api.parse.com/1/batch', true]
1557+
);
1558+
done();
1559+
});
1560+
jest.runAllTicks();
1561+
await flushPromises();
1562+
1563+
xhrs[0].responseText = JSON.stringify([
1564+
{ success: { objectId: 'pid0' } },
1565+
{ success: { objectId: 'pid1' } },
1566+
{ success: { objectId: 'pid2' } },
1567+
{ success: { objectId: 'pid3' } },
1568+
{ success: { objectId: 'pid4' } },
1569+
{ success: { objectId: 'pid5' } },
1570+
{ success: { objectId: 'pid6' } },
1571+
{ success: { objectId: 'pid7' } },
1572+
{ success: { objectId: 'pid8' } },
1573+
{ success: { objectId: 'pid9' } },
1574+
{ success: { objectId: 'pid10' } },
1575+
{ success: { objectId: 'pid11' } },
1576+
{ success: { objectId: 'pid12' } },
1577+
{ success: { objectId: 'pid13' } },
1578+
{ success: { objectId: 'pid14' } },
1579+
{ success: { objectId: 'pid15' } },
1580+
{ success: { objectId: 'pid16' } },
1581+
{ success: { objectId: 'pid17' } },
1582+
{ success: { objectId: 'pid18' } },
1583+
{ success: { objectId: 'pid19' } },
1584+
]);
1585+
xhrs[0].onreadystatechange();
1586+
jest.runAllTicks();
1587+
await flushPromises();
1588+
1589+
xhrs[1].responseText = JSON.stringify([
1590+
{ success: { objectId: 'pid20' } },
1591+
{ success: { objectId: 'pid21' } },
1592+
]);
1593+
xhrs[1].onreadystatechange();
1594+
jest.runAllTicks();
1595+
});
1596+
15341597
it('returns the first error when saving an array of objects', async (done) => {
15351598
const xhrs = [];
15361599
for (let i = 0; i < 2; i++) {
@@ -1714,6 +1777,81 @@ describe('ObjectController', () => {
17141777
await result;
17151778
});
17161779

1780+
it('can destroy an array of objects with batchSize', async () => {
1781+
const objectController = CoreManager.getObjectController();
1782+
const xhrs = [];
1783+
for (let i = 0; i < 3; i++) {
1784+
xhrs[i] = {
1785+
setRequestHeader: jest.fn(),
1786+
open: jest.fn(),
1787+
send: jest.fn()
1788+
};
1789+
xhrs[i].status = 200;
1790+
xhrs[i].responseText = JSON.stringify({});
1791+
xhrs[i].readyState = 4;
1792+
}
1793+
let current = 0;
1794+
RESTController._setXHR(function() { return xhrs[current++]; });
1795+
let objects = [];
1796+
for (let i = 0; i < 5; i++) {
1797+
objects[i] = new ParseObject('Person');
1798+
objects[i].id = 'pid' + i;
1799+
}
1800+
const result = objectController.destroy(objects, { batchSize: 20}).then(async () => {
1801+
expect(xhrs[0].open.mock.calls[0]).toEqual(
1802+
['POST', 'https://api.parse.com/1/batch', true]
1803+
);
1804+
expect(JSON.parse(xhrs[0].send.mock.calls[0]).requests).toEqual([
1805+
{
1806+
method: 'DELETE',
1807+
path: '/1/classes/Person/pid0',
1808+
body: {}
1809+
}, {
1810+
method: 'DELETE',
1811+
path: '/1/classes/Person/pid1',
1812+
body: {}
1813+
}, {
1814+
method: 'DELETE',
1815+
path: '/1/classes/Person/pid2',
1816+
body: {}
1817+
}, {
1818+
method: 'DELETE',
1819+
path: '/1/classes/Person/pid3',
1820+
body: {}
1821+
}, {
1822+
method: 'DELETE',
1823+
path: '/1/classes/Person/pid4',
1824+
body: {}
1825+
}
1826+
]);
1827+
1828+
objects = [];
1829+
for (let i = 0; i < 22; i++) {
1830+
objects[i] = new ParseObject('Person');
1831+
objects[i].id = 'pid' + i;
1832+
}
1833+
const destroy = objectController.destroy(objects, { batchSize: 20 });
1834+
jest.runAllTicks();
1835+
await flushPromises();
1836+
xhrs[1].onreadystatechange();
1837+
jest.runAllTicks();
1838+
await flushPromises();
1839+
expect(xhrs[1].open.mock.calls.length).toBe(1);
1840+
xhrs[2].onreadystatechange();
1841+
jest.runAllTicks();
1842+
return destroy;
1843+
}).then(() => {
1844+
expect(JSON.parse(xhrs[1].send.mock.calls[0]).requests.length).toBe(20);
1845+
expect(JSON.parse(xhrs[2].send.mock.calls[0]).requests.length).toBe(2);
1846+
});
1847+
jest.runAllTicks();
1848+
await flushPromises();
1849+
1850+
xhrs[0].onreadystatechange();
1851+
jest.runAllTicks();
1852+
await result;
1853+
});
1854+
17171855
it('can destroy an array of objects', async () => {
17181856
const objectController = CoreManager.getObjectController();
17191857
const xhrs = [];

0 commit comments

Comments
 (0)