Page 1 of 2 12 LastLast
Results 1 to 10 of 11

Thread: [JS] TimeoutChainer

  1. #1
    Join Date
    May 2007
    Location
    USA
    Posts
    373
    Thanks
    2
    Thanked 4 Times in 4 Posts

    Default [JS] TimeoutChainer

    1) CODE TITLE: TimeoutChainer

    2) AUTHOR NAME/NOTES: Trinithis

    3) DESCRIPTION: Replaces for-loop closures to chain window.setTimeout()'s among other things.

    4) URL TO CODE: http://trinithis.awardspace.com/Time...eoutChainer.js

    Demos:
    http://trinithis.awardspace.com/Time...ner/demo1.html
    http://trinithis.awardspace.com/Time...ner/demo2.html

    I made the TimeoutChainer class to avoid excessive closure use when staging a sequence of setTimeout()'s. It is supposed to simplify creating such a task.

    -------------------------------

    How To:
    Code:
    //Object Arguments
    
    {
      callback: null,
      context: null,
      interval: 0,
      delay: -1, //translates to this.interval
      numTimes: 1,
      args: [],
      runAfter: null
    }
    This is the default object used for TimeoutChainer's object argument. When calling the constructor, you send in one argument that is an object. If that object has a property name that is the same as in the above object, TimeoutChainer will use it's value. If it does not have a corresponding property, then the default object's property value will be used instead.

    • callback: The function to execute at each timeout. You must supply this argument. It may either be a function reference or a string.
    • context: An object reference which the callback uses for the this keyword. Primarily will be used for method callbacks. Use null if there is no need.
    • interval: The time delay between each callback.
    • delay: When to call the first callback. After that times depend on interval. Negative values get translated into the interval time.
    • numTimes: The number of times to call the callback function.
    • args: Holds the arguments callback will use.
    • runAfter: If an object is specified, will only start the timeouts when that object tells it to start. If the object is a TimeoutChainer (TC), THIS will automatically start its timeouts when TC finishes its timeouts. (delay and interval still come into effect.) Demo1 makes use of this.


    Among other things, a variety of properties and methods are also avaliable. It is safe to change most of the supplied "object arguments" whenever you want (... as long as your logic is done correctly). If you change the following properties:

    • callback: Must be a function reference. Cannot use a string this time.
    • numTimes, remaining, completed: Once the timeouts stop and you change these, you have to initiate the timeouts again through the chainTimeout() method. Note: Only remaining is actually used to determine whether or not to stop the timeouts, and that happens when it reaches 0.
    • runAfter: Changing this may cause serious bugs. Leave it alone unless you understand how the constructor uses it.


    Methods:
    • clear(): Clears any existing timeout and will prevent further from being issued until chainTimeout() is called. Sets remaining to 0. Will not change completed. This method will also invoke any TimeoutChainers waiting on this to complete.
    • chainTimeout(): Will initiate the TimeoutChainer. This will always chain at least one timeout even if remaining equals 0. Invoking this method might cause bugs if you supplied a TimeoutChainer to runAfter in the constructor or if you call it while it is still issuing timeouts. You probably should create a new TimeoutChainer instead of calling this method.


    Static Member:
    • TimeoutChainer.SELF: For the args property of the object arguments, you can use this solitary enum constant. The constructor will translate it into a reference to itself. Only use during the constructor. In addition, there really is no need since you can supply an actual reference after constructed. Demo2 makes use of this static member.


    -------------------------------

    Example used in demo1:
    Code:
    var list = document.getElementById("list");
    
    list.appendText = function(s) {
      var li = document.createElement("li");
      li.appendChild(document.createTextNode(s));
      list.appendChild(li);
    }
    
    var tc = new TimeoutChainer({
      callback: "appendText",
      args: [{i:1, toString:function(){return String(this.i++)}}],
      context: list,
      interval: 100,
      numTimes: 10
    });
    
    new TimeoutChainer({
      callback: list.appendText,
      args: [{i:10, toString:function(){return String(this.i--)}}],
      context: list,
      interval: 100,
      delay: 100,
      numTimes: 10,
      runAfter: tc
    });
    Example used in demo2:
    Code:
    //Just think of TextFormatter as a nice way to print stuff on the screen
    
    var tf1 = new TextFormatter(document.getElementById("div1")).setFontFamily("monospace");
    var tf2 = new TextFormatter(document.getElementById("div2")).setFontFamily("monospace");
    
    function write(s, tc, tf) {
      tf.println(tc.completed + ":" + s);
    }
    
    function clearTC(tc) {
      tc.clear();
    }
    
    setTimeout(
      bundleFunction(
        null, clearTC, new TimeoutChainer({
          numTimes: 10, interval: 100, startDelay: 200,
          callback: write, args: ["hi", TimeoutChainer.SELF, tf1]
        })
      ), 450
    );
    
    setTimeout(
      bundleFunction(
        new TimeoutChainer({
          numTimes: 10, interval: 100,
          callback: "println", args: [{
            i: 1,
            toString: function() {
              return this.i++ + ":bye";
            }
          }], context: tf2
        }), "clear"
      ), 550
    );
    Last edited by Trinithis; 12-01-2007 at 08:45 AM.
    Trinithis

  2. #2
    Join Date
    Jun 2005
    Location
    英国
    Posts
    11,876
    Thanks
    1
    Thanked 180 Times in 172 Posts
    Blog Entries
    2

    Default

    Handy.
    Twey | I understand English | 日本語が分かります | mi jimpe fi le jbobau | mi esperanton komprenas | je comprends français | entiendo español | tôi ít hiểu tiếng Việt | ich verstehe ein bisschen Deutsch | beware XHTML | common coding mistakes | tutorials | various stuff | argh PHP!

  3. #3
    Join Date
    May 2007
    Location
    USA
    Posts
    373
    Thanks
    2
    Thanked 4 Times in 4 Posts

    Default

    Updated.

    Should its completed property increment itself before or after the callback? Currently, it increments after, but I am not sure which would be more useful.
    Last edited by Trinithis; 11-30-2007 at 08:04 PM.
    Trinithis

  4. #4
    Join Date
    Jun 2005
    Location
    英国
    Posts
    11,876
    Thanks
    1
    Thanked 180 Times in 172 Posts
    Blog Entries
    2

    Default

    Before. The user might want to do something in the callback if, e.g., it's been completed five times, and if you increment it after the callback they'd have to check if it's four, not five, which is unintuitive.

    One practice I've taken to recently involves using an object to implement named arguments. This is handy because it grants the user a lot more freedom about which arguments to specify. Also, an array of arguments would probably be more useful.

    setTimeout2() and setInterval2() aren't semantic names at all. Also, this is already implemented in Firefox. I suggest overriding the defaults where necessary.
    Twey | I understand English | 日本語が分かります | mi jimpe fi le jbobau | mi esperanton komprenas | je comprends français | entiendo español | tôi ít hiểu tiếng Việt | ich verstehe ein bisschen Deutsch | beware XHTML | common coding mistakes | tutorials | various stuff | argh PHP!

  5. #5
    Join Date
    May 2007
    Location
    USA
    Posts
    373
    Thanks
    2
    Thanked 4 Times in 4 Posts

    Default

    Changed the incrementation policy. Same goes for this.remaining decrementation.

    Also, this is already implemented in Firefox.
    The arguments part is implemented, but you still get errors when using call or apply. Suggestions?

    I'm not sure what you mean by objects for named arguments. Perhaps an example?
    Trinithis

  6. #6
    Join Date
    Jun 2005
    Location
    英国
    Posts
    11,876
    Thanks
    1
    Thanked 180 Times in 172 Posts
    Blog Entries
    2

    Default

    How do you mean using call() or apply()? On setTimeout() itself?
    Code:
    function combine() {
      for(var r = {}, i = arguments.length - 1, x; i >= 0; --i)
        for(x in arguments[i])
          if(arguments[i].hasOwnProperty(x))
            r[x] = arguments[i][x];
    
      return r;
    }
    
    function areaOfRect(args) {
      args = combine(args || {}, {
        width: 50,
        height: 20
      });
    
      return args.width * args.height;
    }
    It can then be called:
    Code:
    // 100 * default 20 == 2000
    areaOfRect({width: 100});
    // default 50 * 400 == 20000
    areaOfRect({height: 400});
    // 100 * 300 == 30000
    areaOfRect({width: 100, height: 300});
    // default 50 * default 20 == 1000
    areaOfRect();
    It allows the arguments to be specified in any order, thereby allowing greater flexibility over which arguments are passed and which are left out, as well as being more readable (the arguments are labeled rather than being an apparently-random string of numbers).
    Last edited by Twey; 12-01-2007 at 12:35 AM.
    Twey | I understand English | 日本語が分かります | mi jimpe fi le jbobau | mi esperanton komprenas | je comprends français | entiendo español | tôi ít hiểu tiếng Việt | ich verstehe ein bisschen Deutsch | beware XHTML | common coding mistakes | tutorials | various stuff | argh PHP!

  7. #7
    Join Date
    May 2007
    Location
    USA
    Posts
    373
    Thanks
    2
    Thanked 4 Times in 4 Posts

    Default

    Interesting. I really like the idea . Will implement.

    This is what I mean by call and apply
    Code:
    var o = {
       val: "o's value",
       display: function() {
          alert(this.val);
       }
    };
    
    setTimeout.call(o, o.display, 100); //error!
    
    //setTimeout2 or whatever it's going to be called
    
    setTimeout2.call(o, o.display, 100); //works
    Trinithis

  8. #8
    Join Date
    Jun 2005
    Location
    英国
    Posts
    11,876
    Thanks
    1
    Thanked 180 Times in 172 Posts
    Blog Entries
    2

    Default

    But why would you ever want to do that? setTimeout() doesn't modify its context, I don't see why you'd want to give it a different context.
    Twey | I understand English | 日本語が分かります | mi jimpe fi le jbobau | mi esperanton komprenas | je comprends français | entiendo español | tôi ít hiểu tiếng Việt | ich verstehe ein bisschen Deutsch | beware XHTML | common coding mistakes | tutorials | various stuff | argh PHP!

  9. #9
    Join Date
    May 2007
    Location
    USA
    Posts
    373
    Thanks
    2
    Thanked 4 Times in 4 Posts

    Default

    Code:
    var o = {
      val: "o's value",
      display: function() {
        alert(this.val);
      }
    };
    
    setTimeout(o.display, 100);  //"undefined"
    I just thought changing the this context was more sugary through the call method rather than through an explicit argument:
    Code:
    setTimeout(o.display, 100, o);
    
    vs.
    
    setTimeout.call(o, o.display, 100);
    This might not seem so much sugar, but when mixing up default/custum contexts with variable arguments, I think it does indeed become apparent. For example
    Code:
    setTimeout(document.write, 100, document, "write");
    setTimeout(alert, 100, null, "alert");
    
    vs.
    
    setTimeout.call(document, document.write, 100, "write");
    setTimeout(alert, 100, "alert"); //no need for anything special here
    Maybe it's just me that thinks the latter is more elegant, but others might not think so. In that case, I'll change it.

    Your combine is buggy. Perhaps you meant to iterate the loop from the last argument to the first argument? Does defining x outside the for-in make it faster?
    Code:
    function combine() {
      for(var r = {}, i = arguments.length-1, x; i >= 0; --i)
        for(x in arguments[i])
          if(arguments[i].hasOwnProperty(x))
            r[x] = arguments[i][x];
    
      return r;
    }
    BTW, thanks for all your help and input so far.
    Last edited by Trinithis; 12-01-2007 at 12:44 AM. Reason: added a missing argument
    Trinithis

  10. #10
    Join Date
    Jun 2005
    Location
    英国
    Posts
    11,876
    Thanks
    1
    Thanked 180 Times in 172 Posts
    Blog Entries
    2

    Default

    Your combine is buggy. Perhaps you meant to iterate the loop from the last argument to the first argument?
    It is, and I did.
    Does defining x outside the for-in make it faster?
    No, I originally had two for/ins so I put it outside to avoid redeclaration. May as well have all the declarations in one place anyway.
    Maybe it's just me that thinks the latter is more elegant, but others might not think so. In that case, I'll change it.
    What you've done there is an abuse of call(). If a function's context is modified with call(), it's expected that the function itself will do something with that context. However, in your code it's just passed onto the argument.

    I suggest a separate function:
    Code:
    function bundleFunction(context, func, args) {
      context = context || null;
    
      if(typeof func === "string" && context)
        func = context[func];
    
      if(!args)
        args = [];
      else if(!(args instanceof Array))
        args = [args];
    
      return function() {
        return func.apply(context, args);
      };
    }
    Then:
    Code:
    setTimeout(bundleFunction(document, "write", "some text"), 100);
    Last edited by Twey; 12-01-2007 at 12:46 AM.
    Twey | I understand English | 日本語が分かります | mi jimpe fi le jbobau | mi esperanton komprenas | je comprends français | entiendo español | tôi ít hiểu tiếng Việt | ich verstehe ein bisschen Deutsch | beware XHTML | common coding mistakes | tutorials | various stuff | argh PHP!

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •