Skip to content

Commit 9845031

Browse files
committed
Add in memory cache, prevent raise conditions
1 parent 820751b commit 9845031

File tree

3 files changed

+114
-59
lines changed

3 files changed

+114
-59
lines changed

integration/test/ParseEventuallyQueueTest.js

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ const Parse = require('../../node');
55
const sleep = require('./sleep');
66

77
describe('Parse EventuallyQueue', () => {
8+
beforeEach(async () => {
9+
await Parse.EventuallyQueue.clear();
10+
});
11+
812
it('can queue save object', async () => {
913
const object = new TestObject({ test: 'test' });
1014
await object.save();
@@ -59,7 +63,7 @@ describe('Parse EventuallyQueue', () => {
5963
length = await Parse.EventuallyQueue.length();
6064
assert.strictEqual(length, 0);
6165

62-
// TODO: can't use obj1, etc because they don't have an id
66+
// TODO: Properly handle SingleInstance
6367
await Parse.EventuallyQueue.destroy(results[0]);
6468
await Parse.EventuallyQueue.destroy(results[1]);
6569
await Parse.EventuallyQueue.destroy(results[2]);
@@ -134,13 +138,30 @@ describe('Parse EventuallyQueue', () => {
134138
assert.strictEqual(hashes[0].get('foo'), 'bar');
135139
});
136140

141+
it('can queue same object but override undefined fields', async () => {
142+
const object = new Parse.Object('TestObject');
143+
object.set('foo', 'bar');
144+
object.set('test', '1234');
145+
await Parse.EventuallyQueue.save(object);
146+
147+
object.set('foo', undefined);
148+
await Parse.EventuallyQueue.save(object);
149+
150+
const length = await Parse.EventuallyQueue.length();
151+
assert.strictEqual(length, 1);
152+
153+
const queue = await Parse.EventuallyQueue.getQueue();
154+
assert.strictEqual(queue[0].object.foo, 'bar');
155+
assert.strictEqual(queue[0].object.test, '1234');
156+
});
157+
137158
it('can poll server', async () => {
138159
const object = new TestObject({ test: 'test' });
139160
await object.save();
140161
object.set('foo', 'bar');
141162
await Parse.EventuallyQueue.save(object);
142163
Parse.EventuallyQueue.poll();
143-
assert.ok(Parse.EventuallyQueue.polling);
164+
assert.ok(Parse.EventuallyQueue.isPolling());
144165

145166
await sleep(4000);
146167
const query = new Parse.Query(TestObject);
@@ -149,7 +170,7 @@ describe('Parse EventuallyQueue', () => {
149170

150171
const length = await Parse.EventuallyQueue.length();
151172
assert.strictEqual(length, 0);
152-
assert.strictEqual(Parse.EventuallyQueue.polling, undefined);
173+
assert.strictEqual(Parse.EventuallyQueue.isPolling(), false);
153174
});
154175

155176
it('can clear queue', async () => {
@@ -171,13 +192,13 @@ describe('Parse EventuallyQueue', () => {
171192
parseServer.server.close(async () => {
172193
await object.saveEventually();
173194
let length = await Parse.EventuallyQueue.length();
174-
assert(Parse.EventuallyQueue.polling);
195+
assert(Parse.EventuallyQueue.isPolling());
175196
assert.strictEqual(length, 1);
176197

177198
await reconfigureServer({});
178199
await sleep(3000); // Wait for polling
179200

180-
assert.strictEqual(Parse.EventuallyQueue.polling, undefined);
201+
assert.strictEqual(Parse.EventuallyQueue.isPolling(), false);
181202
length = await Parse.EventuallyQueue.length();
182203
assert.strictEqual(length, 0);
183204

@@ -197,13 +218,13 @@ describe('Parse EventuallyQueue', () => {
197218
parseServer.server.close(async () => {
198219
await object.destroyEventually();
199220
let length = await Parse.EventuallyQueue.length();
200-
assert(Parse.EventuallyQueue.polling);
221+
assert(Parse.EventuallyQueue.isPolling());
201222
assert.strictEqual(length, 1);
202223

203224
await reconfigureServer({});
204225
await sleep(3000); // Wait for polling
205226

206-
assert.strictEqual(Parse.EventuallyQueue.polling, undefined);
227+
assert.strictEqual(Parse.EventuallyQueue.isPolling(), false);
207228
length = await Parse.EventuallyQueue.length();
208229
assert.strictEqual(length, 0);
209230

src/EventuallyQueue.js

Lines changed: 63 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ type QueueObject = {
2525

2626
type Queue = Array<QueueObject>;
2727

28+
const QUEUE_KEY = 'Parse/Eventually/Queue';
29+
let queueCache = [];
30+
let dirtyCache = true;
31+
let polling = undefined;
32+
2833
/**
2934
* Provides utility functions to queue objects that will be
3035
* saved to the server at a later date.
@@ -33,9 +38,6 @@ type Queue = Array<QueueObject>;
3338
* @static
3439
*/
3540
const EventuallyQueue = {
36-
localStorageKey: 'Parse/Eventually/Queue',
37-
polling: undefined,
38-
3941
/**
4042
* Add object to queue with save operation.
4143
*
@@ -103,9 +105,9 @@ const EventuallyQueue = {
103105
let index = this.queueItemExists(queueData, queueId);
104106
if (index > -1) {
105107
// Add cached values to new object if they don't exist
106-
for (const prop in queueData[index].object.attributes) {
108+
for (const prop in queueData[index].object) {
107109
if (typeof object.get(prop) === 'undefined') {
108-
object.set(prop, queueData[index].object.attributes[prop]);
110+
object.set(prop, queueData[index].object[prop]);
109111
}
110112
}
111113
} else {
@@ -114,7 +116,7 @@ const EventuallyQueue = {
114116
queueData[index] = {
115117
queueId,
116118
action,
117-
object,
119+
object: object.toJSON(),
118120
serverOptions,
119121
id: object.id,
120122
className: object.className,
@@ -124,20 +126,28 @@ const EventuallyQueue = {
124126
return this.setQueue(queueData);
125127
},
126128

129+
store(data) {
130+
return Storage.setItemAsync(QUEUE_KEY, JSON.stringify(data));
131+
},
132+
133+
load() {
134+
return Storage.getItemAsync(QUEUE_KEY);
135+
},
136+
127137
/**
128-
* Returns the queue from local storage
138+
* Sets the in-memory queue from local storage and returns.
129139
*
130140
* @function getQueue
131141
* @name Parse.EventuallyQueue.getQueue
132142
* @returns {Promise<Array>}
133143
* @static
134144
*/
135145
async getQueue(): Promise<Array> {
136-
const q = await Storage.getItemAsync(this.localStorageKey);
137-
if (!q) {
138-
return [];
146+
if (dirtyCache) {
147+
queueCache = JSON.parse((await this.load()) || '[]');
148+
dirtyCache = false;
139149
}
140-
return JSON.parse(q);
150+
return queueCache;
141151
},
142152

143153
/**
@@ -149,7 +159,8 @@ const EventuallyQueue = {
149159
* @ignore
150160
*/
151161
setQueue(queue: Queue): Promise<void> {
152-
return Storage.setItemAsync(this.localStorageKey, JSON.stringify(queue));
162+
queueCache = queue;
163+
return this.store(queueCache);
153164
},
154165

155166
/**
@@ -165,7 +176,6 @@ const EventuallyQueue = {
165176
const index = this.queueItemExists(queueData, queueId);
166177
if (index > -1) {
167178
queueData.splice(index, 1);
168-
await this.setQueue(queueData);
169179
}
170180
},
171181

@@ -178,7 +188,8 @@ const EventuallyQueue = {
178188
* @static
179189
*/
180190
clear(): Promise {
181-
return Storage.setItemAsync(this.localStorageKey, JSON.stringify([]));
191+
queueCache = [];
192+
return this.store([]);
182193
},
183194

184195
/**
@@ -216,18 +227,22 @@ const EventuallyQueue = {
216227
* @static
217228
*/
218229
async sendQueue(): Promise<boolean> {
219-
const queueData = await this.getQueue();
230+
const queue = await this.getQueue();
231+
const queueData = [...queue];
232+
220233
if (queueData.length === 0) {
221234
return false;
222235
}
223236
for (let i = 0; i < queueData.length; i += 1) {
224-
const ObjectType = ParseObject.extend(queueData[i].className);
225-
if (queueData[i].id) {
226-
await this.reprocess.byId(ObjectType, queueData[i]);
227-
} else if (queueData[i].hash) {
228-
await this.reprocess.byHash(ObjectType, queueData[i]);
237+
const queueObject = queueData[i];
238+
const { id, hash, className } = queueObject;
239+
const ObjectType = ParseObject.extend(className);
240+
if (id) {
241+
await this.process.byId(ObjectType, queueObject);
242+
} else if (hash) {
243+
await this.process.byHash(ObjectType, queueObject);
229244
} else {
230-
await this.reprocess.create(ObjectType, queueData[i]);
245+
await this.process.create(ObjectType, queueObject);
231246
}
232247
}
233248
return true;
@@ -283,21 +298,22 @@ const EventuallyQueue = {
283298
*
284299
* @function poll
285300
* @name Parse.EventuallyQueue.poll
301+
* @param [ms] Milliseconds to ping the server. Default 2000ms
286302
* @static
287303
*/
288-
poll() {
289-
if (this.polling) {
304+
poll(ms: number = 2000) {
305+
if (polling) {
290306
return;
291307
}
292-
this.polling = setInterval(() => {
308+
polling = setInterval(() => {
293309
const RESTController = CoreManager.getRESTController();
294310
RESTController.ajax('GET', CoreManager.get('SERVER_URL')).catch(error => {
295311
if (error !== 'Unable to connect to the Parse API') {
296312
this.stopPoll();
297313
return this.sendQueue();
298314
}
299315
});
300-
}, 2000);
316+
}, ms);
301317
},
302318

303319
/**
@@ -308,14 +324,30 @@ const EventuallyQueue = {
308324
* @static
309325
*/
310326
stopPoll() {
311-
clearInterval(this.polling);
312-
this.polling = undefined;
327+
clearInterval(polling);
328+
polling = undefined;
329+
},
330+
331+
/**
332+
* Return true if pinging the server.
333+
*
334+
* @function isPolling
335+
* @name Parse.EventuallyQueue.isPolling
336+
* @returns {boolean}
337+
* @static
338+
*/
339+
isPolling(): boolean {
340+
return !!polling;
341+
},
342+
343+
_setPolling(flag: boolean) {
344+
polling = flag;
313345
},
314346

315-
reprocess: {
347+
process: {
316348
create(ObjectType, queueObject) {
317-
const newObject = new ObjectType();
318-
return EventuallyQueue.sendQueueCallback(newObject, queueObject);
349+
const object = new ObjectType();
350+
return EventuallyQueue.sendQueueCallback(object, queueObject);
319351
},
320352
async byId(ObjectType, queueObject) {
321353
const { sessionToken } = queueObject.serverOptions;
@@ -332,7 +364,7 @@ const EventuallyQueue = {
332364
if (results.length > 0) {
333365
return EventuallyQueue.sendQueueCallback(results[0], queueObject);
334366
}
335-
return EventuallyQueue.reprocess.create(ObjectType, queueObject);
367+
return EventuallyQueue.process.create(ObjectType, queueObject);
336368
},
337369
},
338370
};

0 commit comments

Comments
 (0)