Skip to content

Instantly share code, notes, and snippets.

@gfosco
Last active April 5, 2022 22:11
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save gfosco/7a55d58d8036b21a3cab to your computer and use it in GitHub Desktop.
Save gfosco/7a55d58d8036b21a3cab to your computer and use it in GitHub Desktop.
// 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.');
});
});
@cmcdonaldca
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment