// The before save handler will pass information to the after save handler to disable the // after save handler from creating a loop. // It also prevents client side code from triggering the silent change, by using a different flag // that the client should never see. // It should only be visible in the data browser, won't be sent to a client in an undefined state. Parse.Cloud.beforeSave('TestObject', function(request, response) { handleComingFromTask(request.object); response.success(); }); Parse.Cloud.afterSave('TestObject', function(request) { if (!didComeFromTask(request.object)) { var params = ['a',1]; var objects = [request.object]; taskCreator('test', 'hello', params, objects).then(function() { console.log('Created helloTask for this object.') }, function(err) { console.log(err); }); } }); function handleComingFromTask(object) { object.unset('silenced_afterSave'); if (object.has('should_silence_afterSave')) { object.unset('should_silence_afterSave'); object.set('silenced_afterSave', true); } } function setComingFromTask(object) { object.set('should_silence_afterSave', true); } function didComeFromTask(object) { return object.has('silenced_afterSave'); } var WorkTask = Parse.Object.extend('WorkTask'); // taskCreator will create a WorkTask record to be processed by a background job. // it can accept parameters (strings, numbers, etc.) and also an array of parse objects function taskCreator(taskType, taskAction, params, objects) { var task = new WorkTask(); var targetParams = []; var targetObjects = []; if (params && params.length) { targetParams = params; } if (objects && objects.length) { targetObjects = objects; } return task.save({ 'taskType' : taskType, 'taskAction' : taskAction, 'taskParameters' : targetParams, 'taskObjects' : targetObjects, 'taskClaimed' : 0, 'taskStatus' : 'new', 'taskMessage' : '' }, { useMasterKey : true }); } // Available actions are defined here and link to their function. var WorkActions = { 'hello' : helloTask }; // The helloTask will just update itself with the objectId of the parameter object for a success state. function helloTask(task, params, objects) { var testObject = {}; var changes = {}; if (objects && objects.length) { testObject = objects[0]; // lets just increment a field on the TestObject, and be able to save that change without causing an infinite loop. testObject.increment('someCounter'); setComingFromTask(testObject); return testObject.save(null, { useMasterKey : true }).then(function(testObject) { changes = { 'taskStatus' : 'done', 'taskMessage' : 'Hello ' + testObject.id }; return task.save(changes, { useMasterKey : true }); }); } else { changes = { 'taskStatus' : 'invalid', 'taskMessage' : 'object was not passed.' }; return task.save(changes, { useMasterKey : true }); } } // This background job is scheduled, or run ad-hoc, and processes outstanding tasks. // TODO: Expand to use a scheduled job property to pick queue. Parse.Cloud.job('testQueue', function(request, status) { Parse.Cloud.useMasterKey(); var query = new Parse.Query("WorkTask"); query.equalTo('taskClaimed', 0); query.include('taskObjects'); var processed = 0; query.each(function(task) { // This block will return a promise which is manually resolved to prevent errors from bubbling up. var promise = new Parse.Promise(); processed++; var params = task.get('taskParameters'); var objects = task.get('taskObjects'); // The taskClaimed field is atomically incremented to ensure that it is processed only once. task.increment('taskClaimed'); task.save().then(function(task) { var action = task.get('taskAction'); // invalid actions not defined by WorkActions are discarded and will not be processed again. if (task.get('taskClaimed') == 1 && WorkActions[action]) { WorkActions[action](task, params, objects).then(function() { promise.resolve(); }, function() { promise.resolve(); }); } else { promise.resolve(); } }); return promise; }).then(function() { status.success('Processing completed. (' + processed + ' tasks)') }, function(err) { console.log(err); status.error('Something failed! Check the cloud log.'); }); });