8

OVERVIEW

I'm working on a project and I've come across a bit of a problem in that things aren't happening in the order I want them to happen. So I have been thinking about designing some kind of Queue that I can use to organize function calls and other miscellaneous JavaScript/jQuery instructions used during start-up, i.e., while the page is loading. What I'm looking for doesn't necessarily need to be a Queue data structure but some system that will ensure that things execute in the order I specify and only when the previous task has been completed can the new task begin.

I've briefly looked at the jQuery Queue and the AjaxQueue but I really have no idea how they work yet so I'm not sure if that is the approach I want to take... but I'll keep reading more about these tools.

SPECIFICS

Currently, I have set things up so that some work happens inside $(document).ready(function() {...}); and other work happens inside $(window).load(function() {...});. For example,

<head>
  <script type="text/javascript">    

    // I want this to happen 1st
    $().LoadJavaScript();
    // ... do some basic configuration for the stuff that needs to happen later...

    // I want this to happen 2nd
    $(document).ready(function() {

      // ... do some work that depends on the previous work do have been completed
      var script = document.createElement("script");
      // ... do some more work...
    });

    // I want this to happen 3rd
    $(window).load(function() {

      // ... do some work that depends on the previous work do have been completed
      $().InitializeSymbols();
      $().InitializeBlock();
      // ... other work ... etc...
    });
  </script>
</head>

... and this is really tedious and ugly, not to mention bad design. So instead of dealing with that mess, I want to design a pretty versatile system so that I can, for example, enqueue $().LoadJavaScript();, then var script = document.createElement("script");, then $().InitializeSymbols();, then $().InitializeBlock();, etc... and then the Queue would execute the function calls and instructions such that after one instruction is finished executing, the other can start, until the Queue is empty instead of me calling dequeue repeatedly.

The reasoning behind this is that some work needs to happen, like configuration and initialization, before other work can begin because of the dependency on the configuration and initialization steps to have completed. If this doesn't sound like a good solution, please let me know :)

SOME BASIC WORK

I've written some code for a basic Queue, which can be found here, but I'm looking to expand its functionality so that I can store various types of "Objects", such as individual JavaScript/jQuery instructions and function calls, essentially pieces of code that I want to execute.

UPDATE

With the current Queue that I've implemented, it looks like I can store functions and execute them later, for example:

// a JS file... 
$.fn.LoadJavaScript = function() {

    $.getScript("js/Symbols/Symbol.js");
    $.getScript("js/Structures/Structure.js");
};

// another JS file...
function init() { // symbols and structures };

// index.html
var theQueue = new Queue();
theQueue.enqueue($().LoadJavaScript);
theQueue.enqueue(init);

var LJS = theQueue.dequeue();
var INIT = theQueue.dequeue();

LJS();
INIT();

I also think I've figured out how to store individual instructions, such as $('#equation').html(""); or perhaps even if-else statements or loops, by wrapping them as such:

theQueue.enqueue(function() { $('#equation').html(""); // other instructions, etc... });

But this approach would require me to wait until the Queue is done with its work before I can continue doing my work. This seems like an incorrect design. Is there a more clever approach to this? Also, how can I know that a certain function has completed executing so that the Queue can know to move on? Is there some kind of return value that I can wait for or a callback function that I can specify to each task in the Queue?

WRAP-UP

Since I'm doing everything client-side and I can't have the Queue do its own thing independently (according to an answer below), is there a more clever solution than me just waiting for the Queue to finish its work?

Since this is more of a design question than a specific code question, I'm looking for suggestions on an approach to solving my problem, advice on how I should design this system, but I definitely welcome, and would love to see, code to back up the suggestions :) I also welcome any criticism regarding the Queue.js file I've linked to above and/or my description of my problem and the approach I'm planning to take to resolve it.

Thanks, Hristo

Hristo
  • 45,559
  • 65
  • 163
  • 230
  • For learning about the queue, check out [Can somebody explain jQuery queue to me?](http://stackoverflow.com/questions/1058158/can-somebody-explain-jquery-queue-to-me). There are some great examples of using custom queues there. – justkt Jan 12 '11 at 17:04
  • Yes, I've been reading that actually, along with another post, but I'm not sure if it is what I'm looking for. – Hristo Jan 12 '11 at 17:09
  • Personally, I think when you start getting in to "how do I reorganize how javascript is being run (and what order)" questions, it's time to rethink your logic. Something tells me you need to step back, rethink your process and approach and come to a better conclusion. There's always another way to get done what you need to achieve (and as specific as you were, this question is still too vague to give you a conclusive answer without directly solving the "make a queue system" task at hand). – Brad Christie Jan 12 '11 at 18:28
  • I think you're right, which is why I posted this question... as a first step to rethinking the logic. I've identified that I've taken a wrong path and I'm trying to correct it before its too late. That being said, I'm not looking for you to give me this system, but help me with design ideas that I'm not familiar with that you might be that can be beneficial to the problem I've described. I'm reaching out to this community to brainstorm a good approach and like you said, to come to a better conclusion. – Hristo Jan 12 '11 at 18:31

4 Answers4

3

I would suggest using http://headjs.com/ It allows you to load js files in parallel, but execute them sequentially, essentially the same thing you want to do. It's pretty small, and you could always use it for inspiration.

I would also mention that handlers that rely on execution order are not good design. I am always able to place all my bootstrap code in the ready event handler. There are cases where you'd need to use the load handler if you need access to images, but it hasn't been very often for me.

Ruan Mendes
  • 90,375
  • 31
  • 153
  • 217
  • I need execution order though because I'm using a third-party library that needs to be configured when the page loads before any work can be done using the library. – Hristo Jan 12 '11 at 23:02
  • 1
    head.js should work, it load all the scripts and executes them sequentially. Just make sure all your scripts are external and load them using head.js – Ruan Mendes Jan 12 '11 at 23:03
  • .. this looks cool. I'll definitely read it over and play with it. At the moment though, I want to develop my own solution purely because its part of my learning process. I'm sure Head.js is more efficient and a better solution than what I come with, but it is nice to also learn from mistakes along the way. – Hristo Jan 12 '11 at 23:29
  • ... my Queue is failing :( I'm giving HEAD.js a shot! :) – Hristo Jan 13 '11 at 02:41
  • You can always just use head.js for inspiration by digging through their code – Ruan Mendes Jan 13 '11 at 15:12
2

Here is something that might work, is this what you're after?

var q = (function(){
    var queue = [];
    var enqueue = function(fnc){
        if(typeof fnc === "function"){
            queue.push(fnc);
        }
    };

    var executeAll = function(){
        var someVariable = "Inside the Queue";
        while(queue.length>0){
            queue.shift()();
        }
    };

    return {
        enqueue:enqueue,
        executeAll:executeAll 
    };

}());

var someVariable = "Outside!"
q.enqueue(function(){alert("hi");});
q.enqueue(function(){alert(someVariable);});
q.enqueue(function(){alert("bye");});
alert("test");
q.executeAll();

the alert("test"); runs before anything you've put in the queue.

david
  • 17,925
  • 4
  • 43
  • 57
  • Yeah, I guess this is similar to what I'm looking for, but I've already implemented a Queue that can do what you've shown me. But it looks to me like if I want to execute any individual instructions, I'd have to wrap them in `function() {...}` like you have. – Hristo Jan 12 '11 at 22:24
  • 1
    Wrapping them in the `function(){}` has the added benefit of creating a closure so you maintain the correct scope. I'll edit the example to show why this matters. There, even though you're running the function inside the queue, it still accesses the correct `someVariable` that you declared outside. – david Jan 12 '11 at 22:31
  • Nice! That looks great. Thanks! So that answers my questions regarding "how can I run code with my Queue?". I guess all that remains is to answer "how can I automate the Queue such that this executeAll method knows when one execution is fully completed before the next can start?". Refer to the paragraph right before the WRAP-UP section for more details on that question. – Hristo Jan 12 '11 at 22:40
  • That is a slightly odd question, because javascript is single threaded. Your executeAll method knows when one execution is fully completed because the next line (after you call the function in the queue) will execute. If you want your functions in the queue to be able to run callbacks and things, or use setTimeout/setInterval then things start getting tricky. – david Jan 12 '11 at 22:45
  • gotcha... I guess that is what I was asking though. Say I have the following logic, `var test = theQueue.dequeue(); test(); var init = theQueue.dequeue();`, then the second dequeue will happen only after `test()` is finished running right? – Hristo Jan 12 '11 at 23:32
  • Yes, execution only continues to the `var init=` line after the call to `test();` returns. – david Jan 12 '11 at 23:52
1
**The main functions**

**From there we can define the main elements required:**

var q=[];//our queue container
var paused=false; // a boolean flag
function queue() {}
function dequeue() {}
function next() {}
function flush() {}
function clear() {}

**you may also want to 'pause' the queue. We will therefore use a boolean flag too. 
Now let's see the implementation, this is going to be very straightforward:**

var q      = [];
var paused = false;

function queue() {
 for(var i=0;i< arguments.length;i++)
     q.push(arguments[i]);
}
function dequeue() {
    if(!empty()) q.pop();
}
function next() {
    if(empty()) return; //check that we have something in the queue
    paused=false; //if we call the next function, set to false the paused
    q.shift()(); // the same as var func = q.shift(); func();
}
function flush () {
    paused=false;
    while(!empty()) next(); //call all stored elements
}
function empty() {  //helper function
    if(q.length==0) return true;
    return false;
}
function clear() {
    q=[];
}

**And here we have our basic queue system!
let's see how we can use it:**

queue(function() { alert(1)},function(){ alert(2)},function(){alert(3)});
next(); // alert 1
dequeue(); // the last function, alerting 3 is removed
flush(); // call everything, here alert 2
clear(); // the queue is already empty in that case but anyway...
vijayliebe
  • 158
  • 6
1

how do I store pieces of code in the Queue and have it execute later

Your current implementation already works for that. There are no declared types in JavaScript, so your queue can hold anything, including function objects:

queue.enqueue(myfunc);
var f = queue.dequeue();
f();

how can I have the Queue do its own thing independently

JavaScript is essentially single-threaded, meaning only one thing can execute at any instant of time. So the queue can't really operate "independently" of the rest of your code, if that is what you mean.

You basically have two choices:

  1. Run all the queued functions, one after the other, in a single go -- this doesn't even require a queue since it is the same as simply putting the function calls directly in your code.

  2. Use timed events: run one function at a time and once it completes, set a timeout to execute the next queued function after a certain interval. An example of this follows.


function run() {
  var func = this.dequeue();
  func();

  var self = this;
  setTimeout(function() { self.run(); }, 1000);
}

If func is an asynchronous request, you'll have to move setTimeout into the callback function.

casablanca
  • 69,683
  • 7
  • 133
  • 150
  • This looks pretty good, thanks! I have a couple of questions... firstly, what if an element in the Queue looked like `var script = document.createElement("script");`, then that isn't really a function. How would I execute this instruction? Also, what do you mean by moving the `setTimeout` into the callback function... which callback function? – Hristo Jan 12 '11 at 17:48
  • @Hristo: I'm not really sure what you mean with the `script` example. Can you put it into an anonymous function? `var script = function(){document.createElement("script");};` – Sasha Chedygov Jan 12 '11 at 21:25
  • @musicfreak... surely I can store functions and execute them later. But what I'm asking is, can I store individual instructions, maybe something like `$('#equation').html("");` or `var targetID = $('#equation').find('.active').attr("id");` or if-else statements or for/while loops? I guess I could wrap everything in anonymous functions but it doesn't seem practical? – Hristo Jan 12 '11 at 21:32
  • @casablanca... can you also explain what the `var self = this;` does? I understand that this is pretty much like a recursive function in that `run()` will be called every second but what is 'self'? – Hristo Jan 12 '11 at 22:25
  • 2
    @Hristo: For the first question, you can always wrap a single line or a loop or whatever into an anonymous function: `function() { something; }`. Be careful with variables -- if you mark them as `var`, they will be local to the function, otherwise global. About `var self = this`, that is just an additional reference to the queue object `this`, because inside `setTimeout`, `this` refers to the window. – casablanca Jan 12 '11 at 22:52