Skip to content

Instantly share code, notes, and snippets.

@jswartwood
Created January 6, 2012 14:30
Show Gist options
  • Save jswartwood/1570839 to your computer and use it in GitHub Desktop.
Save jswartwood/1570839 to your computer and use it in GitHub Desktop.
Hiccup?
<!DOCTYPE html>
<html>
<body>
<p>These should output "last", "ok", then "all" for 10 promises and then another 10.</p>
<div id="out"></div>
<script type="text/javascript" src="https://raw.github.com/briancavalier/when.js/dev/when.js"></script>
<script type="text/javascript" src="main.js"></script>
</body>
</html>
var out = document.getElementById("out")
, lastPromise
;
for (var i = 0; i < 10; i++) {
(function( pos ) {
var andDefer = when.defer()
, allPromises = [ lastPromise, andDefer ]
;
when(lastPromise).then(function() {
out.innerHTML += "last done: " + pos + "<br>";
});
lastPromise = when.all(allPromises).then(function() {
out.innerHTML += "all done: " + pos + "<br>";
});
andDefer.then(function( val ) {
out.innerHTML += val + ": " + pos + "<br>";
});
setTimeout(function() {
andDefer.resolve("ok");
}, 1000);
})(i);
}
setTimeout(function() {
out.innerHTML += "<br>";
for (var i = 0; i < 10; i++) {
(function( pos ) {
var andDefer = when.defer()
, allPromises = [ lastPromise, andDefer ]
;
when(lastPromise).then(function() {
out.innerHTML += "last done: " + pos + "<br>";
});
lastPromise = when.all(allPromises).then(function() {
out.innerHTML += "all done: " + pos + "<br>";
});
andDefer.then(function( val ) {
out.innerHTML += val + ": " + pos + "<br>";
});
setTimeout(function() {
andDefer.resolve("ok");
}, 1000);
})(i);
}
}, 10000);
@jswartwood
Copy link
Author

The unanswered question is, why does "last done: 1" appear before "ok: 0".

Is really my main issue. The "all" happening before or after the "ok" is not as much of an issue (as long as it is relatively consistent). But I can't have an initializing method for the second task run before the cleanup/done method for the previous task.

Also, I'm not sure that I really expressed the scenario well enough leading into this discussion. An application may make a call to a queue method:

queue: function( andOne, runNow ) {
    var useAnd = typeof andOne === "function"
        , andDefer = when.defer()
        , allPromises = [ lastPromise, andDefer ]
    ;

    if (useAnd && runNow) {
        andOne(andDefer);
    } else {
        when(lastPromise).then(function() {
            if (useAnd) {
                andOne(andDefer);
            } else {
                andDefer.resolve(andOne);
            }
        });
    }

    lastPromise = when.all(allPromises);

    return andDefer;
}

To be used like:

andOne
    .queue(function( one ) {
        // Execute some setup stuff
        console.log("start testing...");
        // Start something async
        setTimeout(function() {
            one.resolve("test");
        }, 4000);
    })
    .then(function() {
        // This happens when done
        console.log("test done");
    })

These queue calls can be called in the same code-pass (like the loop above) or later like starting the second loop.

I will look into a rewrite with the reduce, but I'm not sure it will be as applicable since I don't really have list of promises.

Fyi a slightly more complete semi-real-world use-case example: https://gist.github.com/1572043
Also posting a link to my "kick" solve (since that may wind up being my permanent fix): http://jsfiddle.net/jswartwood/pCLrg/5/

@briancavalier
Copy link

Ah, ok, thanks for the extra example ... now I understand (I hope!) what you're trying to do. I think it can be simplified by using when() and taking advantage of Promises/A results forwarding. I've found that once I got my brain around those two things, I very rarely need to actually create a deferred with when.defer(). In my experience, it simplifies the code a lot when you simply return values or promises (gotten from calling some other function, possibly) back up the stack instead of creating deferreds and passing them down through the stack.

Here's a working example of the kind of queueing I think you're getting at. It relies on the when() + return up the stack model. Try it out in node and let me know if this does what you need. If not, let's definitely discuss more ... this is a pretty interesting case!

var when = require('./when');

var lastPromise;
function queue(andOne, runNow) {

    var execute = typeof andOne === 'function'
        ? andOne
        : function() { return andOne; };

    lastPromise = runNow
        ? execute()
        : when(lastPromise, execute);

    return lastPromise;
}

// ...
// Use it

function functionToQueue() {
    console.log('start testing...');

    // I'm not sure what your setTimeout here is for, but typically, you just
    // want to return something useful here that will be consumed by the next
    // callback in the chain
    // But, in order to *simulate* something async here, i.e. to delay any subsequent
    // callbacks in the chain, we'll just do:
    var d = when.defer();
    setTimeout(function () {
        d.resolve('test');
        // 'test' will be forwarded to the next callback in this promise's chain
        // It may not be obvious what "this promise" is in this structure, tho
    }, 4000);

    // If you don't care about the 'test' value getting forwarded, you can do:
    //setTimeout(d.resolve, 4000);

    // Very important to return the promise here!
    return d.promise;
}

// Kick off the first test
when(queue(functionToQueue),
    function() {
        // This will be called after the setTimeout
        console.log('test 1 done');
    }
);

// Immediately kick off the 2nd test, which should wait until test 1 is done before starting
when(queue(functionToQueue),
    function() {
        // This will be called after the setTimeout
        console.log('test 2 done');
    }
);

@briancavalier
Copy link

I'm betting you know this already, but these two are essentially equivalent:

when(something).then(doCoolStuff);

// is equivalent to

when(something, doCoolStuff);

The second one will create 1 fewer promise, and so be (negligibly) faster, and will probably compress slightly better. Otherwise, you won't notice a difference.

@jswartwood
Copy link
Author

Nice. Yes, that is more akin to what I was looking for, although the API for the module got a little twisted in all of that. I've just done a new push to https://github.com/jswartwood/and1 to finish out my first-draft prototype. Now, I just need a little simplification (if possible); I'll be looking to merge in aspects of your Promises/A example above to clean out some of those pesky defers (I even ended up adding another in jswartwood/and1@13b3cb7). I'm going to throw some doc in the code when I get home to explain my intentions for the usage.

Also, I'm thinking that any further queue conversation should probably move to jswartwood/and1#1; it has become less about the initial hiccup (of misordered "last done: 1" vs "ok: 0") and more about the actual queue implementation.

@briancavalier
Copy link

Cool. Feel free to @ me on that issue if you have any more when.js questions.

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