UNPKG

qjs

Version:

Use the await keyword with Q promises to tame your async code

224 lines (201 loc) 9.1 kB
var falafel = require('../falafel'); var templatesetc = require('../templates'); var template = templatesetc.templates; var constant = templatesetc.constants; var errors = constant.errors; var secretPrefix = templatesetc.secretPrefix; var promiseID = 0; var stepID = 1; var statementTransformers = {}; var toTransformUsingParent = [ 'AssignmentExpression', 'BinaryExpression', 'CallExpression', 'UpdateExpression', 'ExpressionStatement', 'MemberExpression', 'ReturnStatement', 'VariableDeclarator', 'VariableDeclaration', 'LogicalExpression']; toTransformUsingParent.forEach(function (key) { statementTransformers[key] = transformUsingParent; }); function transformUsingParent(node) { if (node.needsToResolve && node.needsToResolve.length) { var toResolve = node.parent.needsToResolve || (node.parent.needsToResolve = []); node.needsToResolve.forEach(function (await) { toResolve.push(await); }); } node.parent.needsTransformation = node.parent.needsTransformation || node.needsTransformation; } statementTransformers['IfStatement'] = function transformIfStatement(node) { node.update(template.ifElseStatement({ test: node.test.source(), consequent: removeBraces(node.consequent.source()), alternate: node.alternate ? removeBraces(node.alternate.source()) : '', consequentStep: stepID++, alternateStep: stepID++, continueStep: stepID++ })); }; statementTransformers['WhileStatement'] = function transformWhileStatement(node) { var needsToResolve = []; if (node.needsToResolve && node.needsToResolve.length) { node.parent.needsTransformation = node.needsTransformation; var buffer = []; node.needsToResolve.forEach(function (promise) { needsToResolve.push({source: promise.source, id: promise.id, stepID: stepID++}); }); node.needsToResolve = null; } else { needsToResolve = null; } node.update(template.whileStatement({ test: node.test.source(), body: removeBraces(node.body.source()), testStep: stepID++, consequentStep: stepID++, alternateStep: stepID++, needsToResolve: needsToResolve })); }; statementTransformers['WithStatement'] = function transformWithStatement(node) { node.update(template.withStatement({ object: node.object.source(), body: removeBraces(node.body.source()), innerStep: stepID++, continueStep: stepID++ })); }; statementTransformers['ForStatement'] = function transformForStatement(node) { var needsToResolveInit = []; var needsToResolveTest = []; var needsToResolveUpdate = []; if (node.needsToResolve && node.needsToResolve.length) { node.parent.needsTransformation = node.needsTransformation; var buffer = []; node.needsToResolve.forEach(function (promise) { var val = {source: promise.source, id: promise.id, stepID: stepID++}; var name = secretPrefix + 'awaitResults[' + promise.id + ']'; if (node.init.source().indexOf(name) !== -1) { needsToResolveInit.push(promise); } else if (node.test.source().indexOf(name) !== -1) { needsToResolveTest.push(val); } else if (node.update.source().indexOf(name) !== -1) { needsToResolveUpdate.push(val); } else { throw new Error('You seem to have an await doing something crazzzyyyy in a for statement.');//this should never actually be thrown... } }); node.needsToResolve = needsToResolveInit; } else { needsToResolveTest = null; needsToResolveUpdate = null; } node.update(template.forStatement({ init: node.init.source(), test: node.test.source(), increment: node.update.source(), body: removeBraces(node.body.source()), testStep: stepID++, incrementStep: stepID++, consequentStep: stepID++, alternateStep: stepID++, needsToResolveUpdate: needsToResolveUpdate, needsToResolve: needsToResolveTest })); }; statementTransformers['TryStatement'] = function transformTryStatement(node) { if (node.finalizer) throw new Error('Finalizers aren\'t supported yet :('); var continueStep = stepID++; node.update(template.tryStatement({ body: removeBraces(node.block.source()), catches: node.handlers.map(function (h) { return h.source().replace(/\}$/, secretPrefix + 'currentStep = ' + continueStep + ';\n}'); }).join('\n'), innerStep: stepID++, continueStep: continueStep })); }; function transformAwaitNode(node) { if (node.arguments.length < 1) throw new Error(errors.missingPromise + ':\n ' + node.source()); if (node.arguments.length > 1) throw new Error(errors.tooManyPromises + ':\n ' + node.source()); transformUsingParent(node); var parent = node.parent; while (parent && parent.type !== 'FunctionDeclaration' && parent.type !== 'FunctionExpression') { parent = parent.parent; } if (!parent) throw new Error('You must put your await expression inside a function.'); parent.asyncWrapNeeded = true; node.parent.needsTransformation = true; (node.parent.needsToResolve = (node.parent.needsToResolve || [])).push({id: promiseID, source: node.arguments[0].source()}); node.update(template.awaitResult({id: promiseID, step:0})); promiseID++; } module.exports.transformNode = transformNode; function transformNode(node) { if (node.needsTransformation || (node.type === 'CallExpression' && node.callee && node.callee.type === 'Identifier' && node.callee.name === 'yield') ) { //console.log('work will be done'); } if (node.type === 'VariableDeclaration') { var closure = node.parent; while (closure && !(closure.type === 'FunctionDeclaration' || closure.type === 'FunctionExpression')) { closure = closure.parent; } if (closure) { var addSemicolon = /\;$/g.test(node.source()); var varsToDeclare = (closure.varsToDeclare = (closure.varsToDeclare || [])); node.declarations.forEach(function (declaration) { varsToDeclare.push(declaration.id.name); }); node.update('(' + node.source().replace(/^var/g, ' ').replace(/\;$/g, '') + ')' + (addSemicolon?';':'')); } } if (node.type === 'CallExpression' && node.callee && node.callee.type === 'Identifier' && node.callee.name === 'yield') { transformAwaitNode(node); } else if (node.asyncWrapNeeded) { if (node.parent.type === 'CallExpression') { } //var prefix = ''; //if (node.type === 'FunctionDeclaration') prefix = 'var ' + node.id.name + ' = '; node.body.update(template.innerStepFunction({ vars: (node.varsToDeclare && node.varsToDeclare.length)?'var ' + node.varsToDeclare.join(', ') + ';':'', name: ((node.id && node.id.name)||''), source: removeBraces(node.body.source()) })); //node.update(prefix + secretPrefix + 'async(' + node.source() + ')'); } else if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') { if(node.varsToDeclare && node.varsToDeclare.length) { node.body.update('{\nvar ' + node.varsToDeclare.join(', ') + ';' + removeBraces(node.body.source()) + '\n}'); } } else if (node.type === 'BlockStatement') { node.parent.needsTransformation = node.parent.needsTransformation || node.needsTransformation; } else if (node.needsTransformation) { node.parent.needsTransformation = node.needsTransformation; if (statementTransformers[node.type]) { statementTransformers[node.type](node); } else { console.log('Node type not handled: ' + node.type); } } if (node.needsTransformation && node.needsToResolve && node.needsToResolve.length && node.parent && node.parent.type === 'BlockStatement') { node.parent.needsTransformation = node.needsTransformation; var buffer = []; node.needsToResolve.forEach(function (promise) { buffer.push(template.await({source: promise.source, id: promise.id, stepID: stepID++})); }); node.needsToResolve = null; node.update(buffer.join('') + node.source()); } } module.exports.compile = compile; function compile(source) { promiseID = 0; stepID = 1; return falafel(source, transformNode).toString(); } function removeBraces(str) { if (/^\{/g.test(str) && /\}$/g.test(str)) { return str.substr(1, str.length - 2); } else { return str; } }