Features
Function Chaining
Sequencr.js was originally built to solve the annoying callback hell problem encountered when chaining computationally expensive blocking functions together.
ThissetTimeout(function(timeout) { function1(); setTimeout(function(timeout) { function2(); setTimeout(function(timeout) { function3(); }, timeout, timeout) }, timeout, timeout) }, 10, 10); |
becomes this.Sequencr.chain([function1, function2, function3], 10); |
But this is also handy for adding visual effects to a page. Hint: click Mario to whack him with a boomerang or give him a red mushroom.
function marioBig(){ $("#mario").css("height", "100px"); } function marioSmall(){ $("#mario").css("height", "70px"); } var changingLock = false; $(function(){ $("#mario").click(function(){ if(changingLock) return; changingLock = true; if($(this).css("height") == "70px"){ Sequencr.chain([ marioBig, marioSmall, marioBig, marioSmall, marioBig, function(){changingLock = false;} ], 100); } else{ Sequencr.chain([ marioSmall, marioBig, marioSmall, marioBig, marioSmall, function(){changingLock = false;} ], 100); } }); }); |
I should mention that jquery is not a dependancy.
Any value returned by a function will be used as an argument to the next function.
The timeout parameter for Sequencr.chain
can be a function that returns an integer.
This way, you can have dynamic timeouts. If a function is used in place of timeout,
that function will be passed a parameter indicating 0-based index of the function in the array.
This can be useful for a number of applications. Suppose you wanted to chain several functions with different timeouts.
You could do something like this:
Sequencr.chain([function1, function2, function3], function(i){return [100, 400, 500][i];});
Non-Blocking Loops
How do you avoid crashing the browser when running CPU-intensive loops? Since JavaScript doesn't have a native sleep() function, the answer is timeouts, of course. I got fed up with the limitations and annoyances of setInterval, clearInterval, and setTimeout, so I built a better solution: Sequencr.for
and Sequencr.do
.
This can be useful for breaking up expensive loops. For instance, checking relatively high prime numbers without degrading user experience (yes it's been processing the entire time while you were up there playing with Mario):
function recursivePrimeFinder(value) { var sqrt = ~~Math.sqrt(value); Sequencr.for(0, sqrt / 1000, function(i){ for(var x = i * 1000 + 2; x < sqrt && x < value; x++){ if(value % x === 0) { return false; //Breaks out of the loop } } }, 0, //Interval function(completed){ //Done callback - completed will be true iff value is prime if(completed){ console.log(value); } recursivePrimeFinder(value + 2); }); } recursivePrimeFinder(10000000001); //Prime numbers > ten billion. |
|
Note that the above example also demonstrates one method of pulling off an asynchronous nested loop, i.e., consider this typical blocking prime finder who's algorithm was used to build the above:
for(var i = 2; i < 1000; i++){ //Test integers from 1 -> 1000 prime = true; for(j = 2; j < ~~Math.sqrt(i); j++)){ //Check factors if(i % j === 0) { prime = false; break; } } if(prime) console.log(i); }
Nesting is a bit tricky when the inner loop is required to be asynchronous. Recursion (or flattening the loop) works,
or use Sequencr.js' built-in promise-based techniques (keep reading).
Sequencr.js' callback-based loops also provide a succinct solution to numerous other common tasks.
sampleText = "The quick brown fox jumps over the lazy dog."; var printLock = false; var timeout = 75; function typeText(){ if(printLock) return; printLock = true; Sequencr.for(0, sampleText.length, function(i){ console.log(sampleText[i]); //Print current char }, timeout, //Timeout in ms function(){printLock = false}); //Done callback } |
|
timeout = function(i){ //i is the index within the loop var timeout = 50; //Min timeout. timeout += 200 * Math.random(); //Random factor if(sampleText[i] == sampleText[i].toUpperCase()){ //Everyone knows uppercase letters take longer to type timeout += 25; } switch(sampleText[i].toLowerCase()){ case ".": case ",": timeout += 100 * Math.random(); //Punctuation penalty break; case "a": case "s": case "d": case "f": case "j": case "k": case "l": case ";": timeout += 10 * Math.random(); //Home row penalty break; case "q": case "z": case "p": case "/": timeout += 60 * Math.random(); //Pinky penalty break; default: timeout += 40 * Math.random(); //Other keys penalty } return timeout; } |
|
Promises
Dealing with code that must make asynchronous requests? You need promises. Sequencr.js has you covered.
We've got Sequencr.promiseChain
, and Sequencr.promiseFor
for your enjoyment. Let's take another look at that prime number example from above because if it were written using Sequencr.promiseFor
, we wouldn't need to use recursion or do any flattening, and that's just what promises are for!
//Infinite promise loops are not possible because the promise chain is set up all at once, //but you could always execute this multiple times on different number ranges. Sequencr.promiseFor(2, 1000, function(resolve, reject, i){ if(i > 3){ Sequencr.promiseFor(2, ~~Math.sqrt(i) + 1, function(resolve, reject, j){ setTimeout(function(){ //Prevent hanging. if(i % j === 0) reject(i); //Break out of internal loop (not prime) else resolve(i); //Continue internal loop (could be prime) }, 0); }) //Returns a promise to the last iteration. .then(function(x){ console.log(x + " is prime."); resolve(); //Continue }).catch(function(x){ //console.log(x + " is not prime."); resolve(); //Continue }); } else{ console.log(i + " is prime."); resolve(); //Continue } }); |
|
Sequencr.promiseChain
is also useful for chaining together functions that make asynchronous requests. For instance:
var myPromise = Sequencr.promiseChain([ function(resolve, reject){ resolve(100); //Arbitrary value. }, function(resolve, reject, x){ resolve(x * 2); }, function(resolve, reject, x){ resolve(x + 10); }, ]); //Whenever/later: myPromise.then(function(x){ console.log(x); //Will print 210, given the above sequence. }); |
See how Sequencr sets up and manages the actual promise objects? All you need to do is call either resolve or reject when your work is done. Simple. Let's try a more practical example.
Sequencr.promiseChain([ //GET AN ACCESS TOKEN function(resolve, reject){ console.log("Getting access token."); $.get("http://jsideris.github.io/Sequencr.js/access_token", {username: "jsideris", password: "123456789"}) .done(function(data){ console.log("Access token is: " + data); resolve(data); //Return, pass access token into next function. }).fail(function(data){reject("Failed to get access token.");}); }, //USE THE TOKEN TO ACCESS PRIVATE CONTENT function(resolve, reject, accessToken){ $.get("http://jsideris.github.io/Sequencr.js/secret_file?accesstoken=" + accessToken) .done(function(data){resolve(data);}) .fail(function(data){reject("Failed get secret file.");}); } ]).then(function(data){ //PRINT FILE TO CONSOLE. console.log("Successfully retrieved file:"); console.log(data); }).catch(function(reason){ //ONE OF THE ABOVE ASYNC REQUESTS FAILED. console.log(reason); }); |