public
Last active

A better, imho, when() function that normalizes its return value to a promise

  • Download Gist
when.js
JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
// An alternative to dojo.when that always returns a promise
// The problem with dojo.when, imho, is that you have no idea
// what it will return, so if you want to chain anything, you
// end up with lots of nested when()s, like this:
// dojo.when(dojo.when(dojo.when(thing)))
//
// This alternative always returns a promise, so you can write:
// when(thing).then().then().then()
// which is much more intuitive, imho.
//
// Note that Promise is probably dojo._base.Deferred if you're
// using dojo.
//
// promised-io has a similar function named whenPromise(),
// so if you use this you may want to adopt its naming convention.
// See: https://github.com/kriszyp/promised-io/blob/master/lib/promise.js
 
function when(valueOrPromise) {
var promise;
 
if(isPromise(valueOrPromise)) {
promise = valueOrPromise;
} else {
promise = new Promise();
promise.resolve(valueOrPromise);
}
 
return promise;
}
 
// An example implementation of isPromise()
function isPromise(valueOrPromise) {
return valueOrPromise && typeof valueOrPromise.then == 'function';
}

This is a great utility, and I've used a similar one many times. I usually call it "thenable", cause it like it's quirky sound :)

/me wonders does it make sense to have an isPromise(obj) function or is sniffing for the "then" method good enough

An isPromise(obj) would at least make for a more resilient implementation.

Dan's got something like this tucked away in Loadrunner as well. https://github.com/danwrong/loadrunner - really great way of storing references to defer the loading of resources

You should defer resolving the promise through a setTimeout (https://gist.github.com/575166) to avoid unpredictable outcomes (https://gist.github.com/575161). fwiw, CJS defines promises through duck-typing.

@unscriptable @cjohansen. Yep, I definitely agree about isPromise() instead of directly testing for the presence of valueOrPromise.then. I was lazy. Thanks for calling me on it :) I'll un-lazify and update it with isPromise()!

Hey @tobie, I def agree on the predictability. I wish there were a standard way to yield the current execution "thread" and continue immediately afterwards. setTimeout adds ~10 msec in most browsers and ~4 msec in Chrome (IIRC). That could be a pretty big performance penalty in some cases. It's hard for me to justify adding it to a general-purpose routine unless it were optional (although I have definitely done it, anyways).

Agreed. The overhead is a pain. We need a process.nextTick in the browser.

@tobie That is an interesting example, for sure. However, I think the "unpredictable" outcome seems more due to invalid assumptions, rather than any inherent problem with promises or my implementation of when() (or dojo's implementation, in fact). I think it is making two invalid assumptions: 1) that the two promises will resolve in the same order every time, and 2) the first promise will always resolve before the value of template is changed. Neither of those, imho is valid for an async programming model.

So, I think deferring with setTimeout may not be necessary. I'd advocate refactoring code like that to remove the assumptions. What do you think? As always, it's entirely possible that I've missed something subtle (or not so subtle!) about your example, so please let me know if I did!

Re: duck-typed promises. Yes, you are absolutely right that CommonJS says they must be feature-testable. In fact, Kris Zyp's CommonJS Promises/A proposal says specifically: "A promise is defined as an object that has a function as the value for the property 'then'" (here: http://wiki.commonjs.org/wiki/Promises/A). Wrapping that test up in a function, in some sense, just saves some typing, and will probably minify better in most compressors. However, if the method for determining whether something is a promise should ever change, it would also help with maintenance, as @unscriptable and @cjohansen hinted at.

The problem I kept running into was having to feature test the return value of dojo.when() which was tedious, or having to nest dojo.when(), which, to my eyes, is harder to read than when().then().then(). That's personal preference, of course.

Better explanation that I could give.

Contrary to popular belief, sync programming is not a subset of async programming. The two should be considered mutually exclusive.

For example, you would have no way to collect the first chunk of data from the following stream if it somehow was cached and emitted synchronously.:

var stream = new Stream();
stream.on('data', doStuff);

"Contrary to popular belief, sync programming is not a subset of async programming. The two should be considered mutually exclusive."

Well said @tobie! :)

As for duck-typed promises, I'm aware of Kris Zyp's proposal and it's wording. I still think isPromise will make the code both easier to understand, and possibly easier to maintain in the long run.

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.