Skip to content

Commit 3164b47

Browse files
cipiripperflovilmart
authored andcommitted
Syncing afterSave/afterDelete trigger calls (Issue #2489) (#2499)
* Implemented syncing afterSave/afterDelete trigger calls with REST request execution flow (Issue 2489). After this change, afterSave and afterDelete triggers CAN return a promise, which needs to be resolved inside a trigger for REST request flow to continue. If trigger doesn't return a promise, request flow continues. * Added {} to multiline if. * Fixed bad commit. * Fixed problem with beforeSave triggers becoming async.
1 parent 430ae37 commit 3164b47

File tree

4 files changed

+182
-14
lines changed

4 files changed

+182
-14
lines changed

spec/CloudCode.spec.js

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,162 @@ describe('Cloud Code', () => {
162162
}, 500);
163163
});
164164

165+
it('test afterSave ran on created object and returned a promise', function(done) {
166+
Parse.Cloud.afterSave('AfterSaveTest2', function(req) {
167+
let obj = req.object;
168+
if(!obj.existed())
169+
{
170+
let promise = new Parse.Promise();
171+
setTimeout(function(){
172+
obj.set('proof', obj.id);
173+
obj.save().then(function(){
174+
promise.resolve();
175+
});
176+
}, 1000);
177+
178+
return promise;
179+
}
180+
});
181+
182+
let obj = new Parse.Object('AfterSaveTest2');
183+
obj.save().then(function(){
184+
let query = new Parse.Query('AfterSaveTest2');
185+
query.equalTo('proof', obj.id);
186+
query.find().then(function(results) {
187+
expect(results.length).toEqual(1);
188+
let savedObject = results[0];
189+
expect(savedObject.get('proof')).toEqual(obj.id);
190+
done();
191+
},
192+
function(error) {
193+
fail(error);
194+
done();
195+
});
196+
});
197+
});
198+
199+
it('test afterSave ignoring promise, object not found', function(done) {
200+
Parse.Cloud.afterSave('AfterSaveTest2', function(req) {
201+
let obj = req.object;
202+
if(!obj.existed())
203+
{
204+
let promise = new Parse.Promise();
205+
setTimeout(function(){
206+
obj.set('proof', obj.id);
207+
obj.save().then(function(){
208+
promise.resolve();
209+
});
210+
}, 1000);
211+
212+
return promise;
213+
}
214+
});
215+
216+
let obj = new Parse.Object('AfterSaveTest2');
217+
obj.save().then(function(){
218+
done();
219+
})
220+
221+
let query = new Parse.Query('AfterSaveTest2');
222+
query.equalTo('proof', obj.id);
223+
query.find().then(function(results) {
224+
expect(results.length).toEqual(0);
225+
},
226+
function(error) {
227+
fail(error);
228+
});
229+
});
230+
231+
it('test afterSave rejecting promise', function(done) {
232+
Parse.Cloud.afterSave('AfterSaveTest2', function(req) {
233+
let promise = new Parse.Promise();
234+
setTimeout(function(){
235+
promise.reject("THIS SHOULD BE IGNORED");
236+
}, 1000);
237+
238+
return promise;
239+
});
240+
241+
let obj = new Parse.Object('AfterSaveTest2');
242+
obj.save().then(function(){
243+
done();
244+
}, function(error){
245+
fail(error);
246+
done();
247+
})
248+
});
249+
250+
it('test afterDelete returning promise, object is deleted when destroy resolves', function(done) {
251+
Parse.Cloud.afterDelete('AfterDeleteTest2', function(req) {
252+
let promise = new Parse.Promise();
253+
254+
setTimeout(function(){
255+
let obj = new Parse.Object('AfterDeleteTestProof');
256+
obj.set('proof', req.object.id);
257+
obj.save().then(function(){
258+
promise.resolve();
259+
});
260+
261+
}, 1000);
262+
263+
return promise;
264+
});
265+
266+
let errorHandler = function(error) {
267+
fail(error);
268+
done();
269+
}
270+
271+
let obj = new Parse.Object('AfterDeleteTest2');
272+
obj.save().then(function(){
273+
obj.destroy().then(function(){
274+
let query = new Parse.Query('AfterDeleteTestProof');
275+
query.equalTo('proof', obj.id);
276+
query.find().then(function(results) {
277+
expect(results.length).toEqual(1);
278+
let deletedObject = results[0];
279+
expect(deletedObject.get('proof')).toEqual(obj.id);
280+
done();
281+
}, errorHandler);
282+
}, errorHandler)
283+
}, errorHandler);
284+
});
285+
286+
it('test afterDelete ignoring promise, object is not yet deleted', function(done) {
287+
Parse.Cloud.afterDelete('AfterDeleteTest2', function(req) {
288+
let promise = new Parse.Promise();
289+
290+
setTimeout(function(){
291+
let obj = new Parse.Object('AfterDeleteTestProof');
292+
obj.set('proof', req.object.id);
293+
obj.save().then(function(){
294+
promise.resolve();
295+
});
296+
297+
}, 1000);
298+
299+
return promise;
300+
});
301+
302+
let errorHandler = function(error) {
303+
fail(error);
304+
done();
305+
}
306+
307+
let obj = new Parse.Object('AfterDeleteTest2');
308+
obj.save().then(function(){
309+
obj.destroy().then(function(){
310+
done();
311+
})
312+
313+
let query = new Parse.Query('AfterDeleteTestProof');
314+
query.equalTo('proof', obj.id);
315+
query.find().then(function(results) {
316+
expect(results.length).toEqual(0);
317+
}, errorHandler);
318+
}, errorHandler);
319+
});
320+
165321
it('test beforeSave happens on update', function(done) {
166322
Parse.Cloud.beforeSave('BeforeSaveChanged', function(req, res) {
167323
req.object.set('foo', 'baz');

src/RestWrite.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -296,10 +296,10 @@ RestWrite.prototype.handleAuthData = function(authData) {
296296
// Login with auth data
297297
delete results[0].password;
298298
let userResult = results[0];
299-
299+
300300
// need to set the objectId first otherwise location has trailing undefined
301301
this.data.objectId = userResult.objectId;
302-
302+
303303
// Determine if authData was updated
304304
let mutatedAuthData = {};
305305
Object.keys(authData).forEach((provider) => {
@@ -309,7 +309,7 @@ RestWrite.prototype.handleAuthData = function(authData) {
309309
mutatedAuthData[provider] = providerData;
310310
}
311311
});
312-
312+
313313
this.response = {
314314
response: userResult,
315315
location: this.location()
@@ -328,7 +328,7 @@ RestWrite.prototype.handleAuthData = function(authData) {
328328
return this.config.database.update(this.className, {objectId: this.data.objectId}, {authData: mutatedAuthData}, {});
329329
}
330330
return;
331-
331+
332332
} else if (this.query && this.query.objectId) {
333333
// Trying to update auth data but users
334334
// are different
@@ -476,7 +476,7 @@ RestWrite.prototype.handleFollowup = function() {
476476
return this.config.database.destroy('_Session', sessionQuery)
477477
.then(this.handleFollowup.bind(this));
478478
}
479-
479+
480480
if (this.storage && this.storage['generateNewSession']) {
481481
delete this.storage['generateNewSession'];
482482
return this.createSessionToken()
@@ -847,7 +847,7 @@ RestWrite.prototype.runDatabaseOperation = function() {
847847
.then(response => {
848848
response.objectId = this.data.objectId;
849849
response.createdAt = this.data.createdAt;
850-
850+
851851
if (this.responseShouldHaveUsername) {
852852
response.username = this.data.username;
853853
}
@@ -895,7 +895,7 @@ RestWrite.prototype.runAfterTrigger = function() {
895895
this.config.liveQueryController.onAfterSave(updatedObject.className, updatedObject, originalObject);
896896

897897
// Run afterSave trigger
898-
triggers.maybeRunTrigger(triggers.Types.afterSave, this.auth, updatedObject, originalObject, this.config);
898+
return triggers.maybeRunTrigger(triggers.Types.afterSave, this.auth, updatedObject, originalObject, this.config);
899899
};
900900

901901
// A helper to figure out what location this operation happens at.
@@ -949,7 +949,7 @@ RestWrite.prototype._updateResponseWithData = function(response, data) {
949949
let responseValue = response[fieldName];
950950

951951
response[fieldName] = responseValue || dataValue;
952-
952+
953953
// Strips operations from responses
954954
if (response[fieldName] && response[fieldName].__op) {
955955
delete response[fieldName];

src/rest.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,7 @@ function del(config, auth, className, objectId, clientSDK) {
8686
objectId: objectId
8787
}, options);
8888
}).then(() => {
89-
triggers.maybeRunTrigger(triggers.Types.afterDelete, auth, inflatedObject, null, config);
90-
return;
89+
return triggers.maybeRunTrigger(triggers.Types.afterDelete, auth, inflatedObject, null, config);
9190
});
9291
}
9392

src/triggers.js

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,15 +158,15 @@ function logTrigger(triggerType, className, input) {
158158
return;
159159
}
160160
logger.info(`${triggerType} triggered for ${className}\nInput: ${JSON.stringify(input)}`, {
161-
className,
161+
className,
162162
triggerType,
163163
input
164164
});
165165
}
166166

167167
function logTriggerSuccess(triggerType, className, input, result) {
168168
logger.info(`${triggerType} triggered for ${className}\nInput: ${JSON.stringify(input)}\nResult: ${JSON.stringify(result)}`, {
169-
className,
169+
className,
170170
triggerType,
171171
input,
172172
result
@@ -175,7 +175,7 @@ function logTriggerSuccess(triggerType, className, input, result) {
175175

176176
function logTriggerError(triggerType, className, input, error) {
177177
logger.error(`${triggerType} failed for ${className}\nInput: ${JSON.stringify(input)}\Error: ${JSON.stringify(error)}`, {
178-
className,
178+
className,
179179
triggerType,
180180
input,
181181
error
@@ -209,7 +209,20 @@ export function maybeRunTrigger(triggerType, auth, parseObject, originalParseObj
209209
Parse.masterKey = config.masterKey;
210210
// For the afterSuccess / afterDelete
211211
logTrigger(triggerType, parseObject.className, parseObject.toJSON());
212-
trigger(request, response);
212+
213+
//AfterSave and afterDelete triggers can return a promise, which if they do, needs to be resolved before this promise is resolved,
214+
//so trigger execution is synced with RestWrite.execute() call.
215+
//If triggers do not return a promise, they can run async code parallel to the RestWrite.execute() call.
216+
var triggerPromise = trigger(request, response);
217+
if(triggerType === Types.afterSave || triggerType === Types.afterDelete)
218+
{
219+
if(triggerPromise && typeof triggerPromise.then === "function") {
220+
return triggerPromise.then(resolve, resolve);
221+
}
222+
else {
223+
return resolve();
224+
}
225+
}
213226
});
214227
};
215228

0 commit comments

Comments
 (0)