UNPKG

queue-conductor

Version:

Queue manager with condition-based listening and task subscription

477 lines (364 loc) 20.3 kB
# Queue.js ## Overview Queue.js is a Promise-like tool with a whole lot more. It aims to allow complete control over your temporal architecture and includes a host of advantages: * malleable task chains * flow control: stop/start, reset , delayed or immediate queue initiation * control of queue from inside the chain and outside (outside control not native to asynch/await) * querying and listening * queue subscriptions * subscriptions to specific tasks in a queue * queue status requests * condition-based task flow, great for video/audio * reusable queues * queueline cloning * timeouts (with timeout contingency functions) * easy multi-level queue architecture (submission of a queue as a task of another queue) * special functions like all, race and animate are built in to the chaining syntax * task naming and search There is no resolve and reject, no catch method, and no promises (though promises may be incorporated in a queue). There is simply a single line of results passed through with internal control of the Queue. A queue does not resolve to a final value, but preserves all the values of all it's tasks. ## Usage (webpack) import {Queue} from 'queue-conductor' var queue = new Queue() .add({ task: p => { console.log(p.result)//result from previous task if any p.control.done() }; }) .kickStart() // start off the queue ## How It Works The Queue constructor is used to construct new Queue instances, to which task objects are submitted to build the queueline. Once the queue is started, it iterates over the task objects stored in the queueline, performing the tasks and storing their results upon resolution. A task object takes the form: { task: func to be executed upon reaching the queue. default: ()=>{} preCondition: func which must be satisfied before the task is executed. Return will be evaluated as truthy or falsey, evaluated at intervals until satisfied default: ()=>true postCondition: func which must be satisfied after the task is executed to move on to the next queue. return value will be evaluated as truthy or falsey. Evaluated at intervals until satisfied. default: ()=>true wait: boolean if false, the function will be executed and will not wait for the resolution, default: true if task object is submitted, false if raw function sec: seconds to wait before moving on, default:3600 timeout: func to be called on after timeout, default: ()=>{} earlyTermination: func to be called on interrupt(), default: ()=>{} getValue(previous result):override func to retrieve value after task execution to be stored as the result of that step, and passed onto the next task. useful for retrieving values after a conditional task, default: false name: 'name of the step', comment, default: task.toString() } All properties are optional. Extra properties permitted, only relevant properties will survive to task insertion. All arguments submitted for incorporation into the queue will be formatted as an object with the above defaults. In addition, properties initiated:false, resolved:false, evaluation:null will be appended to the task. pre and post condition functions are evaluated at intervals (default 175 ms) until satisfied. This interval can be manipulated by calling queueInstance.setCheckSpeed({number} milliseconds). ### Task Function Return Values If the task is not set to wait for a callback, the returned value will be stored as the result of the task. ### Task Arguments The function referenced by the task property in a task object is provided with a control package parameter of the form: { done:func to call to signal the completion of the task. arguments submitted to 'done' are stored as the result of the task result:the result of the previous task control:provides the queueInstance containing the task to which this control package was provided. All methods of the instance are available. You can control the direction of the queue from inside each task! evaluate: Function. Call with a value to set the evaluation property on this task. } The functions referenced by timeout and earlyTermination and getValue are provided the above control package, but without the done function. ### Basic Example Here's an example of a basic 'wait' task object generator: function wait(millisecs){ var task=(p)=>{ setTimeout(()=>{p.done("hello? I'm finished.")},millisecs) } return {task} } new Queue() .add(wait(3000)) .add((p)=>{console.log(p.result)}) .kickStart() //-> waits 3 secs, then outputs:"Hello? I'm finished." Here is an example of queue control from within a task: function wait(millisecs,repeat){ var task=(p)=>{ if(repeat){ p.control .add(wait(millisecs/2)) .add(()=>{console.log("I said I'm finished!")}) } setTimeout(()=>{p.done("hello? I'm finished.")},millisecs) } return {task} } new Queue() .add(wait(3000,true)) .add((p)=>{console.log(p.result)}) .kickStart() //-> after three seconds this example will output:"Hello? I'm finished." After 1500 seconds more, it will output "I said I'm finished!" In the above example we add more wait and output tasks to the end of the queue before setting our timeout! Anything is possible with p.control. All instance methods are available. You can call p.control.insert( someIntermediateTask() ) or call change() to wipe out the remaining tasks and chain up some new ones etc. ## Methods ### Basic initVal(*) sets the value to be passed to the first step in the queueline add(function || task Object || another queue || Promise || single-dimension array of any combination of preceding) adds one or more tasks to the end of the queueline behavior: function: Function is wrapped in a task object with wait set to false. It will be executed when reached in the queue. If the function returns an object, array or function, the return value will be interpreted as a task and inserted into the queueline (as the next step). Otherwise, if the task is not set to wait for a callback, the returned value will be stored as the result of the task. object:interpreted as a task object. absent properties will be populated with default values Queue: resets the queue, starts it, and waits till it is completed to move on. (uses finally on the submitted Queue) Promise: decorates the promise in a task using Promise.finally Array: each index will be added in sequence to the end of the queue insert(see Add) inserts tasks between the current task and the next task in the queue change(see Add) wipes out the remaining steps after the current step, and adds its argument to the queue finally(function) submits a callback function to be called on completion of the queue ### Built-In Methods These methods comprise a growing toolbox of chainable tasks. They add tasks to the end of the queue. all(Array of task Objects, 2 dimensional permitted ) takes an array of functions || task Objects ||Queues || promises || Array of any combination of the preceding and returns an allTask that runs the tasks simultaneously (in respective queues). The allTask does not resolve till all the steps are completed. A second dimension array will be inserted as a sequential queueline with respect to the allTask Ex. 1 new Queue() .all([ ()=>{console.log('this is fine')} wait(150), wait(150), ]) .add(()=>{console.log("I'm finished")}) //outputs 'this is fine', waits 150 milliseconds, then outputs: "I'm finished". All tasks are run concurrently Ex. 2 new Queue() .all([ [()=>{console.log('this is fine too')},wait(100),wait(100)], wait(150), ]) .add(()=>{console.log("I'm finished")}) //outputs 'this is fine too', waits 200 milliseconds then outputs: "I'm finished". The first line is run sequentially, and the second line 'wait(150)' is just outlasted Note: In order to include standard properties on a call to all(), wrap the above described array parameter in an object with 'actions' as it's reference all({ actions:[array of tasks], sec:4, timeout:()=>{ //do stuff on time out } }) race(see all) takes an array of task and returns a race task that runs the tasks simultaneously (in respective queues). The first step to be completed resolves the race Task and advances the queue Ex. new Queue() .race([ [wait(100),wait(100)], wait(50), wait(300) ]) .add(()=>{console.log("I'm finished")}) //waits 50 milliseconds then outputs: "I'm finished" animate(param object) animates any object from current to destination, using javascript driven incrementing parameters:{ src:object to be animated. Any size and configuration dest:object containing destination values. must be congruent with src object. any value not found in corresponding address on src will not be animated orig:optional. object containing values to be animated from. artificial starting point duration:time the animation should take contour:defines the curve the animation should follow. options: linear,smooth,smooth-sine,para(parabolic, abrupt end),root(abrupt beginning),boomerang preAnim: function to be executed before the animation begins postAnim function to be executed after the animation ends preInc: function to be executed before each animation frame postInc: function to be executed after each animation frame } transition(param object) animates the style of a node through css transition parameters: { node:the dom Node whose style will be animated style:{props to be animated} duration:the duration of the animation in seconds timing:'easings' synch:boolean determines whether to merge the transform with the existing transform or replace it } wait(milliseconds || {from:milliseconds, to:milliseconds}) waits a specified duration before moving to the next step. If a range object is submitted, it will choose a random duration between the from and to values submitted on the object. ajax(param object) simple ajax requests parameters:{ url:the url to request from data: object with name value pairs, a desired formNode, or a url encoded string. If a form is submitted with action specified, the form action will be used as url method //default: 'POST' synch:bool //default true } Return value p.result= {response,status} //onload 'axios' and 'fetch' methods are coming soon and will simply port the respective modules through Queue's chainable syntax blink(param object) oscilates a node's opacity between 1 and 0 a specified number of times at a specified rate parameters:{ node:node to blink interval: the time each blink should take in milliseconds repeat:number of times to blink proportion: proportion of each interval the node should be visible (between 0 and 1) } ### InsertBuilt-Ins each of the Built-In functions has an insert version. They take the same arguments, but insert the resulting tasks immediately after the current task. * insertAll * insertRace * insertAnimate * insertTransition * insertWait * insertAjax * insertBlink ### Task Generators Each of the Built-In methods also has a generator attached to the constructor. They take the same arguments as above. They return tasks to be submitted to any queueInstance. * Queue.all * Queue.race * Queue.animate * Queue.transition * Queue.wait * Queue.ajax * Queue.blink ### Flow Control stop() stops the queue kickStart() starts a queue if not running. chainable interrupt() interrupts the current task, executes the earlyTermination function, if provided. chainable setCheckSpeed(milliseconds) sets the interval between preCondition and postCondition checks. chainable ### Queue Editing slice(param object) -> array of tasks returns a slice of the queue line (does not alter queueline) parameters{ from: see find() to: see find() } if the returned indexes are out of order, an empty array is returned splice(param object) removes the queue segment defined by 'from' and 'to' parameters{ from: see find() to: see find() replacement: task || array of tasks } If the from argument stretches back to include current or past tasks, the from indes will be reset to the first future task in the queue. if the returned indexes are out of order, nothing is altered. clear() stops the queue, clears the tasks clearResults(optional:queueline) clears the results of a queueline parameters: any queueline. default is the native queueline. chainable pop() pops off the last step in the queueline. fails if the last task is already started. chainable delete(Parameter Object) deletes the specified step from the queueline. chainable. parameters:name or index find(ObjectParam) Parameter Object may have the following properties: { name, //string, name of the taskObject index, // the index of the taskObject in the queue line taskObj, // the taskObject itself task // the function to be executed } The properties are listed in order of use, i.e. the index will only be searched for if there is no name property searches for a step having the submitted name or index. returns undefined if not found On slicing: copies of the queueLine are returned, and each task is a shallow copy of the original, with results cleared and individual task subscriptions cloned. So a sliced queueline can be filtered, mapped and so on, and the sliced queueline segment will run independently in a new Queue. However, the function references contained in the sliced tasks point to the original functions which may still have closure variables that will be operated upon when reached in a new Queue. Of course, this is problem easily solved by creating Queue/queueline factories, which return new Queues/queuelines free and clear for splicing/running. ### Listening subscribe(function || {cb:function}) subscribes a listener to be executed upon completion of the queue. chainable unsubscribe(function || {cb:function}) unsubscribes a listener. chainable listen(queueInstance || {queue,start:boolean}) listens for the end of a queue if start is true, the queue is kickstarted when it is reached. Start default for both types of arguments is false. chainable Listen is mainly for use when building queue chains someQueueInstance.listen(anotherQueueInstance) //tells someQueueInstance to listen for the completion of anotherQueueInstance before moving on to the next task while subscribe and unsubscribe are mainly for general subscription someQueueInstance.subscribe(myCallBack) //tells someQueueInstance to execute the callBack when finished. ### Querying status() returns an object :{ currentTask, queueLength, queueIndex, waitRunning(boolean, waiting for event trigger), checksRunning(boolean, waiting for condition satisfaction), queueLine:shallow copy of the queueLine array } allDone() returns true if all tasks in the queue have been resolved running() returns true if the queue is running. There is no 'rejected' status. A completed task is marked 'resolved' no matter the result. The next task interprets the result and decides how to proceed. Set the evaluation property on p.thisTask to describe the success or failure of the task to observers. ### Error Handling Because you can control everything about the queueline from the inside and the outside, error handling is once again your responsibility as a developer. You take a result and decide what to do with it. There's one simple line of tasks that you mold how and when you see fit. ### Further Examples ##### Syntax Ex. 1 Add new Queue() .add( task1 ) .add( task2 ) is quivalent to new Queue() .add([task1,task2]) where task1 and task2 are either, function, task object, queue or native Promise. (they don't have to be the same type) Ex. 2 Remember, raw functions move on, and their return values are stored as results. Task objects automatically wait for callbacks, unless a preCondition or postCondition is set. new Queue() 1. .wait(1000) waits a second 2. .add(()=>{alert('ok 1 second in'), return true}) alerts 'ok 1 second in' and moves on. 3. .add((p)=>{ if(p.result===true){ p.control.insert([task1,task2]) } }) Because the previous task was submitted as a raw function, it's return value was interpreted and stored as a result. p.result is therefore true on this task, and task 1 and 2 are inserted and executed sequentially at this point. Wait is set to false for this task because it too came in as a raw function 4. .add((p)=>{if(p.result==true){ p.control.change(new Queue().add(otherTask)) }}) If the previous task had returned true, this task would have wiped out the remainder of the queue, appended new Queue().add(otherTask), and moved on down the new track. Once finished, the original 'finally' callback would have been fired. However, as written, nothing changes in the queue because the previous task did not store any result. 5. .animate(src:node.style,dest:{opacity:0},duration:1000) animates a src node to opacity of 0, over the course of 1 second 6. .add([ //the following are added sequentially as if through separate add calls {task:(p)=>{setTimeout(()=>{p.done('hey')},1000)}}, //wait set to true because the function was clothed in a task object. Resolves immediately. (p)=>{alert(p.result)}, Queue.all([ task4, task5, [task6,task7,task8], Queue.wait(3000) ]), ()=>{alert('done')} ]) this task sets up a callback that will submit string 'hey' as it's result after 1 sec alerts 'hey' and moves on Queue.all returns a task that starts task 4,5,6 and the task returned by Queue.wait concurrently, while tasks 6,7 and 8 run sequentially. The Queue will advance after the completion of the last of: task 4, or task 5 or the sequence of task 6,7 and 8, or 3 seconds elapse. alerts done, and fires the callback registered by finally. ### Testing Tests are run with mocha in the browser. from the command line, navigate to the module folder. (often node_modules/queue-conductor) run webpack --config webpack.mocha_config.js. In your browser visit the subdirectory : dist/tests/run_tests.htm ### Issues No known issues, but splice, slice, find, evaluate(on the control package) and delete are fairly experimental. ### Future Changes * **subscribeTask(Parameter Object, callBack)** The parameter object should suitable for submission to 'find'. It should contain a 'name' or 'index' property, corresponding to the task to which you would like to subscribe (see find above). It will activate a callback when a specific task is completed (experimentally available now) * **listenTask(Parameter Object, queue)** The parameter object should suitable for submission to 'find'. It should contain a 'name' or 'index' property, corresponding to the task to which you would like to subscribe (see find above). It will look for a specific task on another queue and move on when that task is completed (experimentally available now) * **jump(index||name||{steps})** will jump to a specific task. if steps is negative queue will jump backwards, and all task results ahead of the new queue positions will be cleared. * **loop({iterations, passCondition, backTo, failure})** will loop the queue iterations times if passCondition not satisfied, and execute failure if not satisfied within the specified iterations. the 'backTo' argument will specify a step to which to return * **alternate(iterations, passCondition, backTo)** When the queue is finished, will append steps in reverse and keep running. It will do this up to the specified number of iterations. If the passCondition is included and satisfied, the loop will break and the queue will continue. * **continue()** if the queue is in a loop, or alternation, continue() will break it * **Queue.toPromise(function || queue || task Object)** Will convert the argument to a promise and return it * **equip ajax() to handle file uploads, or replace with an existing ajax utility** * **asynch/await-like syntax for asynch functions** * **portal for plugging in your your own custom task generators**