Last active
June 14, 2016 12:46
-
-
Save crrobinson14/1750f3f6e8c0a941751b to your computer and use it in GitHub Desktop.
Wrap ActionHero methods in async() to simplify their internal await() handling.
This file contains 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
exports.action = { | |
name: 'myAction', | |
description: 'Perform some work with multiple async steps.', | |
async: true, | |
run: function(api, data) { | |
var firstRecord = api.await(api.orm.getFirstRecord()); | |
// We will async await this result here before proceeding. No callback nesting or promise chaining! | |
if (!firstRecord) { | |
throw new NotFoundError(); | |
} | |
var secondRecord = api.await(api.orm.getSecondRecord(firstRecord.someKey)); | |
data.response.status = 'OK'; | |
data.response.record = secondRecord; | |
} | |
}; |
This file contains 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
exports.task = { | |
name: 'asyncTask', | |
description: 'Illustrate how to make an asyncawait-driven task.', | |
queue: 'default', | |
frequency: 30000, | |
async: true, | |
run: function(api) { | |
var record = api.await(api.orm.processRecord()); | |
// This will be logged as the task's result in AH's task processing logs | |
return '1 record processed.'; | |
} | |
}; |
This file contains 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
var async = require('asyncawait/async'), | |
await = require('asyncawait/await'), | |
Promise = require('bluebird'), | |
publicApi = {}, | |
api; | |
/** | |
* Helper that logs SQL queries. | |
* | |
* @example | |
* api.models.MYMODEL.findXYZ({ where: {...}, logging: api.helpers.logSQL }); | |
*/ | |
publicApi.logSQL = function(sql) { | |
if (process.env.NODE_ENV === 'test') { | |
console.log(sql); // eslint-disable-line no-console | |
} else { | |
api.log('SQL', 'info', sql); | |
} | |
}; | |
/** | |
* Wrap ActionHero's typical call pattern to clean up async functions. Note that we no longer pass next() in | |
* to the caller. The caller is expected to act in a Promise-style async manner and throw() on an error. | |
* We handle the call to next() ourselves. | |
* @private | |
*/ | |
function asyncActionWrapper(api, data, next) { | |
var error; | |
async(this.originalRun)(api, data).catch(function(e) { | |
// NOTE: In my environment I have typed errors that I can throw. As an additional nice-to-have I use their | |
// names here to set up specific HTTP response codes for specific error types. This is nice because actions | |
// don't need a lot of crufty code to do the same job. If a DB query doesn't find the record the action wants | |
// to work with, it can just throw NotFoundError() and a 404 will be emitted with it automatically. | |
if (e.name === 'NotFoundError') { | |
data.connection.rawConnection.responseHttpCode = 404; | |
} else if (e.name === 'SessionError') { | |
data.connection.rawConnection.responseHttpCode = 401; | |
} else if (e.name === 'RequestError') { | |
data.connection.rawConnection.responseHttpCode = 405; | |
} else if (e.name === 'ValidationError') { | |
data.connection.rawConnection.responseHttpCode = 406; | |
} else { | |
data.connection.rawConnection.responseHttpCode = 500; | |
} | |
error = e; | |
}).finally(function() { | |
next(error); | |
}); | |
} | |
function asyncTaskWrapper(_api, params, next) { | |
async(this.originalRun)(_api, params).then(function(result) { | |
next(null, result); | |
}).catch(function(e) { | |
api.log('Task ' + this.name + ' failed', 'error', e); | |
next(e); | |
}); | |
} | |
module.exports = { | |
initialize: function(_api, next) { | |
api = _api; | |
api.helpers = publicApi; | |
api.Promise = Promise; | |
api.async = async; | |
api.await = await; | |
convertAsyncActions(); | |
convertAsyncTasks(); | |
Object.keys(api.actions.actions).map(function(name) { | |
Object.keys(api.actions.actions[name]).map(function(version) { | |
var actionTemplate = api.actions.actions[name][version]; | |
if (actionTemplate.async) { | |
api.log('Making action ' + name + ':' + version + ' async.', 'info'); | |
actionTemplate.originalRun = actionTemplate.run; | |
actionTemplate.run = asyncApiWrapper.bind(actionTemplate); | |
} | |
}); | |
}); | |
Object.keys(api.tasks.tasks).map(function(name) { | |
var taskTemplate = api.tasks.tasks[name]; | |
if (taskTemplate.async) { | |
api.log('Making task ' + name + ' async.', 'info'); | |
taskTemplate.originalRun = taskTemplate.run; | |
taskTemplate.run = asyncTaskWrapper.bind(taskTemplate); | |
} | |
}); | |
}, | |
start: function(_api, next) { | |
next(); | |
}, | |
stop: function(_api, next) { | |
next(); | |
} | |
}; |
This file contains 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
var action = { | |
name: 'originalAction', | |
description: 'What the action looked like before it was cleaned up.' | |
}; | |
action.run = function(api, data) { | |
// Note that this is technically only 7 lines longer - but that grows for every promise in the chain. An action with | |
// 5-10 promises chained together, or nested callbacks, can have a lot of function(){} closure boilerplate for all | |
// those promises. Asyncawait does away with that boilerplate. | |
api.orm.getFirstRecord().then(function(firstRecord) { | |
if (!firstRecord) { | |
throw new NotFoundError(); | |
} | |
return api.orm.getSecondRecord(firstRecord.someKey); | |
}).then(function(secondRecord) { | |
data.response.status = 'OK'; | |
data.response.record = secondRecord; | |
next(); | |
}).catch(function(e) { | |
next(e); | |
}); | |
}; | |
exports.action = action; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment