Why would you want that?
Say you need to fetch content from a web service via AJAX. You need to wait for the service to return the content before you render it with your client-side templating engine. And you need the template engine to finish rendering before you add content to the DOM. And you need content added to the DOM before you can attach handlers to elements in the page. And you need handlers attached to the page elements before you display it to the user.
Or maybe you have a complex series of animations, each of which needs to finish before the next one begins. Fade this out, then slide this up, then expand that, then fade those other things in.
Can you do all that without Sequencer? Sure, if you like tightly coupled code that isn't modular or reusable. You can just load up your code with callbacks, conditionals, and nested functions until it's a tangled, unmaintainable mess.
But you shouldn't.
DropletJS.Sequencer is available via a number of popular package managers:
npm install dropletjs.sequencer
jam install DropletJS.Sequencer
bower install DropletJS.Sequencer
Or you can download the latest tag from https://github.com/wmbenedetto/DropletJS.Sequencer/tags
If you really want to see Sequencer in action, visit http://demos.wmbenedetto.com/DropletJS.Sequencer/examples/
Or you can download the latest tag (or clone this repo) and open examples/index.html in a browser (preferably Chrome or Firefox with Firebug installed).
Either way, you'll find a ton of explanations with live code samples that you can run. The docs below explain a lot, but they're not as good as seeing it actually working.
The easiest way to use Sequencer is to pass an array of functions to the constructor, then call
varconsole.log'First';seqnext;;varconsole.log'Second';seqnext;;varconsole.log'Third';seqnext;;var sequence = doFirstdoSeconddoThirdrun;// RESULT:// First// Second// Third
Notice that each function in the sequence is passed a
seq argument, which is an instance of the sequence. Inside each function, we call
seq.next(). That's what tells Sequencer that the function's work is done, and the next function should be executed.
seq argument is always passed as the last argument. So, if you have functions that already have other arguments coming in,
seq will come after those. For example:
var// do stuff with arg1 and arg2 ...seqnext;
In the example above, there's really nothing special about Sequencer. You could just call those three functions without Sequencer, and they would work exactly the same.
Where Sequencer becomes important is when one or more of the functions in the sequence have an asynchronous element.
For example, look at
doSecond in the example below:
varconsole.log'First';seqnext;;varconsole.log'Waiting 2 seconds ...';setTimeoutconsole.log'Second';seqnext;bindthis 2000;;varconsole.log'Third';seqnext;;var sequence = doFirstdoSeconddoThirdrun;// RESULT:// First// Waiting 2 seconds ...// Second// Third
As you can see,
doSecond has a 2-second timeout in it. If you were to run those three functions without Sequencer,
you'd end up with
doThird executing before
doSecond had finished its timeout callback.
By using Sequencer, the sequence doesn't move forward until
next() is called. Therefore, execution is essentially paused until
doSecond is finished what it needs to do.
doSecond is done its work (in this case, finished its timeout), it calls
next(), and the sequence can now continue on to
All the examples in this README use global functions, so passing function references to Sequencer is simple: they are just passed by name:
var sequence = doFirstdoSeconddoThirdrun;
However, if your functions aren't global -- like if they are properties of an object -- then you'll need to use
bind() to preserve their scope.
var functions =console.log'First';seqnext;console.log'Second';seqnext;console.log'Third';seqnext;;var steps =functionsdoFirstbindfunctionsfunctionsdoSecondbindfunctionsfunctionsdoThirdbindfunctions;var sequence = stepsrun;// RESULT:// First// Second// Third
If you want to see exactly what Sequencer is doing under the hood, you can turn on console logging and set log levels by passing an options hash to the constructor as a second argument.
Logging is only available in the non-minified version of Sequencer. (It is stripped out of the minified version to reduce file size.)
name option will prepend the name of the sequence to any log messages, which is useful if you're debugging multiple sequences in a page.
logLevel option will control how many and what type of log messages are output. Valid values are:
OFFTurn off all debug logging
ERROROnly output errors
WARNOutput errors and warnings
INFOOutput errors, warnings, and informational messages.
DEBUGOutput all possible log messages
var options =name : 'Example'logLevel : 'DEBUG';var seq = doFirst doSecond doThirdoptionsrun;
If you want to execute a function once all steps of your sequence are complete, you can pass an
onComplete function as part of the options hash.
var options =console.log'DONE!';;var seq = doFirst doSecond doThirdoptionsrun;// RESULT:// First// Waiting 2 seconds ...// Second// Third// DONE!
Sometimes while a sequence is running, something happens that makes you want to stop the sequence before it completes. For example, an AJAX request for a resource fails, and the rest of the sequence cannot run without that resource.
To kill a sequence, simply call the
kill() method on the sequence instance.
To execute a function when a sequence is killed, you can pass an
onKilled function as part of the options hash.
In the example below, a timeout is set to kill the sequence after 1 second. Since
doSecond() has a two-second timeout, the sequence will be killed before that timeout fires.
var options =console.log'KILLED!!';;var seq = doFirst doSecond doThirdoptionsrun;var killTimeout = setTimeoutseqkill;1000;// RESULT:// First// Waiting 2 seconds ...// KILLED!// Second
There may be cases where you want to build up a sequence one step at a time instead of passing an array of functions to the constructor. You can do this by passing a function to the
addStep() function, or an array of functions to the
Functions added using
addSteps() are added to the end of the sequence.
var seq = ;seqaddStepdoFirst;// Do some stuff ...seqaddStepsdoSecond doThird;// Do some more stuff ...seqrun;// RESULT:// First// Waiting 2 seconds ...// Second// Third
Sometimes you need to pass a value between functions in a sequence.
To pass a value to the first function in the sequence, you can pass it as an argument (or arguments) to the
You can continue passing values along the sequence by passing them as arguments to the
Arguments passed to
next() in the last function of the sequence will be passed to the
onComplete() function (if defined).
Of course, in all cases, the functions need to be prepared to accept and use those arguments.
PROTIP: To avoid tightly coupling functions together, write your functions to accept a single data object as the lone argument.
In the example below, a data object has a
counter property. The data object is passed to
run(). Each function then increments the counter and passes the data object to
next(). At the end, the data object ends up in
vardatacounter++;console.log'First:'datacounter;seqnextdata;;vardatacounter++;console.log'Second'datacounter;seqnextdata;;vardatacounter++;console.log'Third'datacounter;seqnextdata;;var options =datacounter++;console.log'onComplete:'datacounter;;var data =counter : 0;var sequence = doFirstdoSeconddoThirdoptionsrundata;// RESULT:// First: 1// Second: 2// Third: 3// onComplete: 4
Because at least some of the functions in a sequence are likely asynchronous, we can't rely on the function
return to pass results from one function to another.
We also may be using functions with arguments that we don't want to (or can't) modify due to some other external dependency, so we may not be able to pass data between functions like in the previous example.
In this case, we can use the
addResult() function to add the results of a function to the sequence, and
getLastResult() to retrieve the results of the previous function in the sequence.
varconsole.log'First.';seqaddResult'foo';seqnext;;varconsole.log'Second. The result of the first function was: '+seqgetLastResult;seqaddResult'bar';seqnext;;varconsole.log'Third. The result of the second function was: '+seqgetLastResult;seqaddResult'baz';seqnext;;var options =console.log'onComplete. The result of the third function was: '+seqgetLastResult;;var sequence = doFirstdoSeconddoThirdoptionsrun;// RESULT:// First.// Second. The result of the first function was: foo// Third. The result of the second function was: bar// onComplete. The result of the third function was: baz
In addition to functions, steps in a sequence can also be other sequences. In other words, you can have a sequence of sequences.
varconsole.log'First';seqnext;;varconsole.log'Second';seqnext;;varconsole.log'Third';seqnext;;var sequence1 = doFirstdoSeconddoThird;var sequence2 = doThirddoSeconddoFirst;var nestedSeq = sequence1sequence2run;// RESULT:// First// Second// Third// Third// Second// First
Sometimes, you may need to modify a sequence while it is running. For example, you may want to add additional steps based on the results of some function in the sequence. There are two ways to do this:
To add a function to the end of the sequence, you can add pass it to
To add multiple functions to the end of a sequence, you can pass them in an array to
varconsole.log'First';seqaddStepdoFirstAdded;seqnext;;varconsole.log'Second';seqaddStepsdoSecondAddeddoThirdAdded;seqnext;;varconsole.log'Third';seqnext;;varconsole.log'Added #1';seqnext;;varconsole.log'Added #2';seqnext;;varconsole.log'Added #3';seqnext;;var sequence = doFirstdoSeconddoThirdrun;// RESULT:// First// Second// Third// Added #1// Added #2// Added #3
You can also insert a single function into the sequence passing it to
insertStep(), or multiple functions by passing them in an array to
This will insert the function(s) as the next in the sequence, with the rest of the sequence continuing in order after the inserted step(s).
varconsole.log'First';seqinsertStepdoFirstInserted;seqnext;;varconsole.log'Second';seqinsertStepsdoSecondInserteddoThirdInserted;seqnext;;varconsole.log'Third';seqnext;;varconsole.log'Inserted #1';seqnext;;varconsole.log'Inserted #2';seqnext;;varconsole.log'Inserted #3';seqnext;;var sequence = doFirstdoSeconddoThirdrun;// RESULT:// First// Inserted #1// Second// Inserted #2// Inserted #3// Third
Make sure all of the functions are receiving
seq as an argument, and are calling
seq.next() when they are done.
Make sure you are only passing function references to Sequencer, and not actually calling the functions.
In other words, you should only be using function names, without parentheses after them.
// Right. Just function names, no parentheses.var sequence = doFirstdoSeconddoThirdrun;// Wrong. Notice the parentheses after each function.var sequence = doFirstdoSeconddoThirdrun;
You probably need to use
bind() when creating your sequence. See [An important note about scope] (https://github.com/wmbenedetto/DropletJS.Sequencer#an-important-note-about-scope).
kill will kill the sequence, but it can't kill a function that is already running.
So if you have, for example, a
setTimeout inside your function, and you call
kill() after that that
setTimeout has already been called, the
setTimeout callback will fire no matter what.
However, the next function in your sequence will not run once
kill() has been called.
Please submit all bugs, questions, and suggestions via the Issues section so everyone can benefit from the answer.
If you need to contact me directly, email email@example.com.
Copyright (c) 2012 Warren Benedetto firstname.lastname@example.org
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.