Skip to content

Commit 6f6c49f

Browse files
committed
Prioritize .then on thenable functios (#735)
1 parent 487da8d commit 6f6c49f

File tree

2 files changed

+39
-6
lines changed

2 files changed

+39
-6
lines changed

lib/dust.js

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -248,16 +248,25 @@
248248
};
249249

250250
/**
251-
* Decide somewhat-naively if something is a Thenable.
251+
* Decide somewhat-naively if something is a Thenable. Matches Promises A+ Spec, section 1.2 “thenable” is an object or function that defines a then method."
252252
* @param elem {*} object to inspect
253253
* @return {Boolean} is `elem` a Thenable?
254254
*/
255255
dust.isThenable = function(elem) {
256-
return elem &&
257-
typeof elem === 'object' &&
256+
return elem && /* Beware: `typeof null` is `object` */
257+
(typeof elem === 'object' || typeof elem === 'function') &&
258258
typeof elem.then === 'function';
259259
};
260260

261+
/**
262+
* Decide if an element is a function but not Thenable; it is prefereable to resolve a thenable function by its `.then` method.
263+
* @param elem {*} target of inspection
264+
* @return {Boolean} is `elem` a function without a `.then` property?
265+
*/
266+
dust.isNonThenableFunction = function(elem) {
267+
return typeof elem === 'function' && !dust.isThenable(elem);
268+
};
269+
261270
/**
262271
* Decide very naively if something is a Stream.
263272
* @param elem {*} object to inspect
@@ -430,7 +439,7 @@
430439
}
431440
}
432441

433-
if (typeof ctx === 'function') {
442+
if (dust.isNonThenableFunction(ctx)) {
434443
fn = function() {
435444
try {
436445
return ctx.apply(ctxThis, arguments);
@@ -747,7 +756,7 @@
747756
};
748757

749758
Chunk.prototype.reference = function(elem, context, auto, filters) {
750-
if (typeof elem === 'function') {
759+
if (dust.isNonThenableFunction(elem)) {
751760
elem = elem.apply(context.current(), [this, context, null, {auto: auto, filters: filters}]);
752761
if (elem instanceof Chunk) {
753762
return elem;
@@ -772,7 +781,7 @@
772781
chunk = this,
773782
i, len, head;
774783

775-
if (typeof elem === 'function' && !dust.isTemplateFn(elem)) {
784+
if (dust.isNonThenableFunction(elem) && !dust.isTemplateFn(elem)) {
776785
try {
777786
elem = elem.apply(context.current(), [this, context, bodies, params]);
778787
} catch(err) {

test/templates/all.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,16 @@ function FalsePromise(err, data) {
2424
return defer.promise;
2525
}
2626

27+
/**
28+
* A naive function factory that adds a thenable that resolves to the given value.
29+
* @param resolvesTo {*} The value passed to the `resolve` then argument
30+
*/
31+
function createThenableFunction(resolvesTo) {
32+
var fn = function () {};
33+
fn.then = function (res) { res(resolvesTo) };
34+
return fn;
35+
}
36+
2737
/**
2838
* A naive Stream constructor that streams the provided array asynchronously
2939
* @param arr {Array<Object|Error>|String} items to be streamed
@@ -914,6 +924,20 @@ return [
914924
expected: "Eventually magic!",
915925
message: "should reserve an async section for a thenable returned from a function"
916926
},
927+
{
928+
name: "thenable section from thenable function",
929+
source: "{#thenable}Eventually poof!{/thenable}",
930+
context: { "thenable": createThenableFunction("poof!") },
931+
expected: "Eventually poof!",
932+
message: "should reserve an async section for a thenable function"
933+
},
934+
{
935+
name: "thenable reference from thenable function",
936+
source: "A {thenable} thing",
937+
context: { "thenable": createThenableFunction("real") },
938+
expected: "A real thing",
939+
message: "should reserve an async reference for a thenable function"
940+
},
917941
{
918942
name: "thenable deep section",
919943
source: "Eventually my {#magic.ally}{delicious}{/magic.ally} will come",

0 commit comments

Comments
 (0)