Last active
April 5, 2022 22:11
-
-
Save gfosco/7a55d58d8036b21a3cab to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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.'); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Fosco, this is awesome! The most insightful part about this entire snippet is using the "increment" to make sure a task is only processed once. (Not to take away from the rest). But, this was the last piece of the puzzle I needed to implement our message queue on parse.com and I can't thank you enough. This gist should not be secret and shared with the world! ;-)
Colin