diff --git a/lib/dust.js b/lib/dust.js index 1d31a6e3..c354ae3d 100644 --- a/lib/dust.js +++ b/lib/dust.js @@ -235,6 +235,16 @@ } } return true; + + /** + * Decide somewhat-naively if something is a Thenable. + * @param elem {*} object to inspect + * @return {Boolean} is `elem` a Thenable? + */ + dust.isThenable = function(elem) { + return elem && + typeof elem === 'object' && + typeof elem.then === 'function'; }; // apply the filter chain and return the output string @@ -655,13 +665,15 @@ Chunk.prototype.reference = function(elem, context, auto, filters) { if (typeof elem === 'function') { // Changed the function calling to use apply with the current context to make sure - // that "this" is wat we expect it to be inside the function + // that `this` is what we expect it to be inside the function elem = elem.apply(context.current(), [this, context, null, {auto: auto, filters: filters}]); if (elem instanceof Chunk) { return elem; } } - if (!dust.isEmpty(elem)) { + if (dust.isThenable(elem)) { + return this.await(elem, context); + } else if (!dust.isEmpty(elem)) { return this.write(dust.filter(elem, auto, filters)); } else { return this; @@ -722,7 +734,9 @@ return skip(this, context); } } - } else if (elem === true) { + } else if (dust.isThenable(elem)) { + return this.await(elem, context, bodies); + } else if (elem === true) { // true is truthy but does not change context if (body) { return body(this, context); @@ -825,6 +839,34 @@ } }; + /** + * Reserve a chunk to be evaluated once a thenable is resolved or rejected + * @param thenable {Thenable} the target thenable to await + * @param context {Context} context to use to render the deferred chunk + * @param bodies {Object} must contain a "body", may contain an "error" + * @return {Chunk} + */ + Chunk.prototype.await = function(thenable, context, bodies) { + var body = bodies && bodies.block, + errorBody = bodies && bodies.error; + return this.map(function(chunk) { + thenable.then(function(data) { + if(body) { + chunk.render(body, context.push(data)).end(); + } else { + chunk.end(data); + } + }, function(err) { + if(errorBody) { + chunk.render(errorBody, context.push(err)).end(); + } else { + dust.log('Unhandled promise rejection in `' + context.getTemplateName() + '`'); + chunk.end(); + } + }); + }); + }; + Chunk.prototype.capture = function(body, context, callback) { return this.map(function(chunk) { var stub = new Stub(function(err, out) { diff --git a/test/core.js b/test/core.js index 9922c51f..28748f5c 100644 --- a/test/core.js +++ b/test/core.js @@ -3,7 +3,7 @@ exports.coreSetup = function(suite, auto) { auto.forEach(function(test) { suite.test(test.name, function(){ - testRender(this, test.source, test.context, test.expected, test.options, test.base, test.error || {}, test.log, test.config); + testRender(this, test.source, test.context, test.expected, test.options, test.base, test.error, test.log, test.config); }); }); @@ -153,7 +153,11 @@ function testRender(unit, source, context, expected, options, baseContext, error } dust.render(name, context, function(err, output) { var log = dust.logQueue; - unit.ifError(err); + if(error) { + unit.contains(error, err.message || err); + } else { + unit.ifError(err); + } if(logMessage) { for(var i=0; i