Provide convenience for returning rejected / resolved Promises. #37

Closed
jonnyreeves opened this Issue May 15, 2012 · 3 comments

Comments

Projects
None yet
2 participants

A couple of times I've come across a situation where a function can return and resolve a promise in the same statement, take the following example code:

function getThingy(id) {
    // See if we have previously fetched this thingy.
    var cachedThingy = this._thingiesByIdMap[id];
    if (cachedThingy) {
        // Good news - no need for a round-trip, we can resolve straight away.
        var dfd = when.defer();
        dfd.resolve(cachedThingy);
        return dfd.promise;
    }
    else {
        // Another method, which returns a Promise, takes care of the actual fetch.
        return this._fetchThingyAsync();
    }
}

To me, the code inside the first if branch feels messy - we are creating a temporary (dfd) and having to split the logic over three lines (yes, I'm charging by the line now! ;) It would be great if we could have a terser syntax which also removed the need for creating a temporary variable, jQuery manage this by allowing the defer method to accept an optional function argument which will be supplied with the created deferred object, eg:

if (cachedThingy) {
    return when.defer(function (dfd) { 
        dfd.resolve(cachedThingy);
    }); // Optionally tack `.promise` on the end (but it's already resolved, so that's moot)
}
else {
    // ... Logic omitted
}

Thanks! :)
Jonny.

Owner

briancavalier commented May 15, 2012

Hey Jonny,

There are actually a few ways to return an already-resolved promise. Without knowing the exact situation, here are a few options that might work for you.

Option 1: Use when()

when() is guaranteed to return a trusted promise, no matter what it's input. So, in the case where you already have an actual result, and you just want to wrap it in an already-resolved promise, you can do:

if (cachedThingy) {
    // Always returns an already resolved promise
    return when(cachedThingy);
}
else {
    // ...
}

This has a couple advantages over constructing a deferred. First, it's really short :) Second, it's more expensive to create a full deferred than just to create a promise, so it'll be slightly faster (although you'll probably never notice!).

Option 2: Return cachedThingy

This forces the caller to use when(), but depending on your situation, it may be just fine. I do this a lot in another one of my projects. Components "higher up" in the app use when(), and lower level components just return either promises or values up the stack.

if (cachedThingy) {
    // Just return the cached value.
    // Caller must use when()
    return cachedThingy;
}
else {
    // ...
}

This is even shorter and more efficient at this level, but obviously puts the responsibility of using when() onto the caller, since this code can now presumably return either a promise or an actual value.

Option 3: Cache the promise instead of the thingy

I've been doing this more and more recently as well. Instead of waiting for a promise to resolve and then caching it's result, sometimes it makes sense just to cache the promise, since internally, promises "cache" their own result. For example:

if (cachedThingyPromise) {
    // Return the cached promise.  Caller is guaranteed to always
    // get a promise, and doesn't need to use when()
    return cachedThingyPromise;
}
else {
    cachedThingyPromise = getThingAsynchronously();
}

Again, depending on your situation, one or more of these might be the way to go. At the very least, option 1 is a way to get an already-resolved promise, rather than needing to add any additional functionality to when.defer().

Let me know if one of those works for you! If not, maybe we can explore your particular situation a bit deeper and figure out something that works.

Brian, you're a hero; not only was your first answer just what I was looking for; but your other options are both valid and provide a tonne of depth - the use of when(value) makes perfect sense after your example.

Just to play devils advocate, is there an easy way to always return a rejected Promise using the current API? (instead of the following snippet)

if (!cachedThingy) {
    // Return a rejected Promise.
    var dfd = when.defer();
    dfd.reject("Value not cached!");
    return dfd.promise;
}
Owner

briancavalier commented May 15, 2012

Awesome, glad that worked for you. After writing that response, it occurred to me that doing when(value) to get a resolved promise probably isn't obvious, and I should really document it :) I'll open another ticket to make sure I remember to do that in the next release.

And yep, to return a rejected promise, as of when.js 1.1.0 or later, you can do return when.reject('Value not cached!');

sjurba referenced this issue in MithrilJS/mithril.js May 9, 2014

Closed

Resolve "then" when promise already resolved. #80

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment