What is this new jQuery.Callbacks Object

    In the not so long-released version of jQuery 1.7, a new Callbacks object has appeared , which will be discussed today.
    The official jQuery.Callbacks documentation describes it as a multi-purpose object, which is a list of callback functions (callbacks are simply called callbacks) and powerful tools for managing this list.

    I looked at the capabilities of this object when it was still only in development, and I must say that it initially had a little more capabilities than was left in the release version. For example, now there is no way to create a queue of callbacks that are called one for each callfire(). Apparently, the jQuery team decided to shorten the code a bit, removing the "unnecessary / rarely used" features in order to save the library weight. This is a small digression into the history of Callbacks, but in the following I will describe only the functions available now and in the end I will write a small possible improvement to this object.

    Appointment


    Before starting a detailed study of this new jQuery.Callbacks object, I want to dwell on what this object is generally for. Quite often, JavaScript code uses callbacks - functions that are called when an event occurs, for example, after the completion of an action, the most striking example is the AJAX request. And at the same time, there is often a need to call not one function, but several at once (it is not known in advance how many, maybe a couple, maybe a couple of dozen, or maybe none at all) - this is a well-known and simple pattern “Observer”. And for such cases, the jQuery.Callbacks object in question is useful. In jQuery itself, this object is used (since version 1.7) inside jQuery.Deferred and jQuery.ajax. Also, jQuery authors made this object publicly available and documented it so that other developers could use it when implementing their own components.

    Constructor: jQuery.Callbacks (flags)


    By calling the constructor, the callbacks object is created, which has a number of methods for managing the callback list.
    The parameter is flagsoptional and allows you to set the parameters of the object, we will consider the possible values ​​of the parameter below.
    var callbacks = $.Callbacks();

    To the created object, callbackswe can now add callback functions to the list, delete them, call them, call them again (if it was not forbidden when creating the object), check the status of the object (whether there was already a call or not yet) using such methods of the object as add(), remove(), fire()and others. callbacks are performed, by the way, in the order they are added to the list.

    I note that this is not a "real" constructor of an instance of the class, so newit is not necessary to use the operator when it is called (and even pointless).

    For this reason, you cannot verify whether the object is an instance of Callbacks in a standard way for JS:
    if (obj instanceof $.Callbacks) {
        obj.add(fn);
    }

    An expression under if always returns false. But you can rely on one of the known methods of this object (or several at once), for example, you can check this way:
    if (obj.fire) {
        obj.add(fn);
    }


    In fact, inside this function, a regular JS-object is created with a certain set of methods that rely on their closure - this is a fairly common way in JavaScript to set private variables that are not accessible outside this pseudo-constructor.

    Also, thanks to such a pseudo-constructor, the methods of this object do not depend on the call context - the object to which they belong, which means that they can be safely assigned to the properties of another object without worrying about changing the context - everything will still work correctly. This is true for all methods except fireit depends on the context, but uses it as the context for executing callbacks from the list, i.e. this method in some cases is not just possible, but it is necessaryAssign the properties of another object with a change of context. For instance:
    var c = $.Callbacks(), obj = {};
    obj.register = c.add;
    obj.register(function() { console.log('fired'); });
    c.fire();
    // output: 'fired'


    Flags


    Note : hereinafter, the words “method call fire()” means the call to execute callbacks from the list including the method fireWith().

    The constructor parameter flagsis a line in which flags can be specified with a space - the options according to which the created object will work callbacks. The following flags are supported:

    once - indicates that the list of callbacks can be executed only once, the second and subsequent method calls fire()will be inconclusive (as done in the deferred object), if this flag is not specified, you can call the method several times fire().

    memory - indicates that it is necessary to remember the parameters of the last method call fire()(and execute callbacks from the list) and immediately execute the added callbacks with the corresponding parameters if they are added after the method call fire()(as is done in the deferred object).

    unique- indicates that each callback can be added to the list only once, a repeated attempt to add the callback to the list will lead to nothing.

    stopOnFalse - indicates that it is necessary to stop the execution of callbacks from the list, if any of them returned false, within the current session of the call fire(). The next method call fire()starts a new callback list execution session, and they will be executed again until one of the list returns falseor until it ends.

    Methods


    Below I will give a list of methods with a brief description, examples are in official docks and for some methods in the next section. In general, the methods are quite simple and behave as expected.

    callbacks.add (callbacks) returns: callbacks - adds callbacks to the list, you can pass several functions (several arguments) or function arrays (you can simultaneously do both) in the arguments of this method, you can even pass nested arrays. All arguments (or array elements) that are not functions are simply ignored. A method (this and some further) returns the context of its call, thereby allowing you to organize a chain of calls of several methods of one object in a row, as is customary in jQuery.

    callbacks.remove (callbacks) returns: callbacks- removes callbacks from the list, and even if the callback was added twice, it will be deleted from both positions. T.O. if you call the method of removing some callback from the list, you can be sure that it is no longer in the list, no matter how many times it is added. You can pass multiple functions at the same time as multiple arguments, you cannot pass arrays, all arguments to non-functions are ignored.

    callbacks.has (callback) returns: boolean - Checks if the specified function is in the callback list.

    callbacks.empty () returns: callbacks - clears the callback list.

    callbacks.disable () returns: callbacks - “disables” the callbacks object, all actions with it will be unsuccessful. At the same time, all methods cease to work:add- does not lead to anything, has- always returns false, etc.

    callbacks.disabled () returns: boolean - checks if the callbacks object is disabled, disable()will return after the call true.

    callbacks.lock () returns: callbacks - captures the current state of the callbacks object relative to the parameters and execution status of the callback list. This method is relevant when using the memory flag and is intended to block only subsequent calls fire(), in other cases it is equivalent to a call disable().
    In detail, this method works like this: if the memory flag is not specified or the method has never been called fire()or the last session of callback execution was interrupted by falseone of them returning , then the call is lock()equivalent to the call disable()(it is called inside) and the call disabled()will return in this case true, otherwise only subsequent calls will be blocked fire()- they will neither lead to the execution of callbacks, nor to the change of the execution parameters of the added callbacks (in the presence of the memory flag ).


    callbacks.locked () returns: boolean - Checks if the callbacks object is fixed by the method lock(), also returns trueafter the call disable().

    callbacks.fireWith ([context] [, args]) returns: callbacks - starts the execution of all callbacks in the list with the specified context and arguments. context - indicates the context of the callback execution (an object accessible through the thisinside of the function). args - an array (namely an array) of arguments passed to the callback.

    callbacks.fire (arguments) returns: callbacks - starts the execution of all callbacks in the list with the call context and arguments of this method. arguments - a list of arguments (not an array, as in the methodfireWith()) Those. the call context and callback arguments are the context and method arguments fire().

    An example of how you can equivalently start executing callbacks with the same parameters and context:
    var callbacks = $.Callbacks(),
        context = { test: 1 };
    callbacks.add(function(p, t) { console.log(this.test, p, t); });
    callbacks.fireWith(context, [ 2, 3 ]);
    // output: 1 2 3
    context.fire = callbacks.fire;
    context.fire(2, 3);
    // output: 1 2 3


    Callbacks from the list are executed in the order in which they were added to this list. After the callbacks are executed with the specified flag once, the list will be cleared, and if the memory flag is not specified or the callbacks were interrupted by return false, the callbacks object will be disabled by the method disable().

    Examples


    Let's see how the flags work on examples. All examples use the following functions:
    function fn1( value ){
        console.log( value );
    }
    function fn2( value ){
        fn1("fn2 says:" + value);
        return false;
    }


    $ .Callbacks ():

    var callbacks = $.Callbacks();
    callbacks.add( fn1 );
    callbacks.fire( "foo" );
    callbacks.add( fn2 );
    callbacks.add( fn1 );
    callbacks.fire( "bar" );
    callbacks.remove( fn2 );
    callbacks.fire( "foobar" );
    console.log(callbacks.disabled());
    /*
    output: 
    foo
    bar
    fn2 says:bar
    bar
    foobar
    foobar
    false
    */

    No flags indicated - quite expected behavior.

    $ .Callbacks ('once'):

    var callbacks = $.Callbacks( "once" );
    callbacks.add( fn1 );
    callbacks.fire( "foo" );
    callbacks.add( fn2 );
    callbacks.add( fn1 );
    callbacks.fire( "bar" );
    callbacks.remove( fn2 );
    callbacks.fire( "foobar" );
    console.log(callbacks.disabled());
    /*
    output: 
    foo
    true
    */

    Everything is clear here - once they completed what was, then nothing happens, no matter what they did, because the list is already disabled.

    $ .Callbacks ('memory'):

    var callbacks = $.Callbacks( "memory" );
    callbacks.add( fn1 );
    callbacks.fire( "foo" );
    callbacks.add( fn2 );
    callbacks.add( fn1 );
    callbacks.fire( "bar" );
    callbacks.remove( fn2 );
    callbacks.fire( "foobar" );
    console.log(callbacks.disabled());
    /*
    output: 
    foo
    fn2 says:foo
    foo
    bar
    fn2 says:bar
    bar
    foobar
    foobar
    false
    */

    Here, it seems, nothing too complicated - after the first execution, each addition of the callback causes it to be executed immediately, and then again we call the execution of the entire list. At the same time, we added one function to the list twice - it works twice.

    $ .Callbacks ('unique'):

    var callbacks = $.Callbacks( "unique" );
    callbacks.add( fn1 );
    callbacks.fire( "foo" );
    callbacks.add( fn2 );
    callbacks.add( fn1 );
    callbacks.fire( "bar" );
    callbacks.remove( fn2 );
    callbacks.fire( "foobar" );
    console.log(callbacks.disabled());
    /*
    output: 
    foo
    bar
    fn2 says:bar
    foobar
    false
    */

    And in this case, the repeated addition of the function fn1was ignored.

    $ .Callbacks ('stopOnFalse'):

    var callbacks = $.Callbacks( "stopOnFalse" );
    callbacks.add( fn1 );
    callbacks.fire( "foo" );
    callbacks.add( fn2 );
    callbacks.add( fn1 );
    callbacks.fire( "bar" );
    callbacks.remove( fn2 );
    callbacks.fire( "foobar" );
    console.log(callbacks.disabled());
    /*
    output: 
    foo
    bar
    fn2 says:bar
    foobar
    foobar
    false
    */

    Callback fn2breaks the execution chain, because returns false.

    These are simple examples, and now let's try playing around with flag combinations - it will be a little more interesting:

    $ .Callbacks ('once memory'):

    var callbacks = $.Callbacks( "once memory" );
    callbacks.add( fn1 );
    callbacks.fire( "foo" );
    callbacks.add( fn2 );
    callbacks.add( fn1 );
    callbacks.fire( "bar" );
    callbacks.remove( fn2 );
    callbacks.fire( "foobar" );
    console.log(callbacks.disabled());
    /*
    output: 
    foo
    fn2 says:foo
    foo
    false
    */

    We see that only the first one worked fire()and the addition of new callbacks led to their immediate execution with the parameters of the first fire().

    $ .Callbacks ('once memory unique'):

    var callbacks = $.Callbacks( "once memory unique" );
    callbacks.add( fn1 );
    callbacks.fire( "foo" );
    callbacks.add( fn2 );
    callbacks.add( fn1 );
    callbacks.fire( "bar" );
    callbacks.remove( fn2 );
    callbacks.fire( "foobar" );
    console.log(callbacks.disabled());
    /*
    output: 
    foo
    fn2 says:foo
    foo
    false
    */

    Here the result is the same, despite the fact that we specified the unique flag and add it twice fn1, the second time adding this function to the list worked, because with the specified flag once after the callbacks are executed, the list is cleared and the memory flag indicates that subsequent additions of callbacks will lead to their immediate execution without being placed on the list, and since the list is empty, the addition of any function is always unique. But this flag will play a role when you try to add several callbacks at a time, among which there are duplicate ones, if you change the 4th line in the previous code as shown below, it will fn2still be executed only once (and without the unique flag it would be executed three times ):
    callbacks.add( fn2, fn2, fn2 );


    $ .Callbacks ('once memory stopOnFalse'):

    var callbacks = $.Callbacks( "once memory stopOnFalse" );
    callbacks.add( fn1 );
    callbacks.fire( "foo" );
    callbacks.add( fn2 );
    callbacks.add( fn1 );
    callbacks.fire( "bar" );
    callbacks.remove( fn2 );
    callbacks.fire( "foobar" );
    console.log(callbacks.disabled());
    /*
    output: 
    foo
    fn2 says:foo
    true
    */

    The return falseblocked all subsequent callbacks and if the once flag is present, it generally disables the callbacks object.

    I will not consider all possible combinations of flags, I tried to choose the most interesting (not very simple) and explain the behavior of callbacks. The remaining combinations can be tested independently, for example, using the blank: http://jsfiddle.net/zandroid/JXqzB/

    Promised improvement


    Improvement, of course, is not at all obligatory and even, perhaps, to some degree far-fetched, do not judge strictly.
    The idea of ​​the improvement is to omit the method call fire(), and instead use the callbacks object itself as a function. To do this, write this function:
    (function($, undefined){
        $.FCallbacks = function(flags, fns) {
            var i = $.type(flags) === 'string' ? 1 : 0,
                callbacks = $.Callbacks(i ? flags : undefined);
            callbacks.add(Array.prototype.slice.call(arguments, i))
            return $.extend(callbacks.fire, callbacks, { fcallbacks: true });
        };
    })(jQuery);

    And without further ado, let's see an example of use:
    function fn1(p1, p2) { console.log('fn1 says:', this, p1, p2); }
    function fn2(p1, p2) { console.log('fn2 says:', this, p1, p2); }
    var callbacks = $.FCallbacks('once', fn1, rn2);
    callbacks.add(fn2);
    callbacks(2, 3);

    Also, the new “designer” has the opportunity to immediately transfer the initial callbacks in the parameters, without an extra call add().
    Well, at work: jsfiddle.net/zandroid/RAVtF

    All with the upcoming holidays, thank you for your attention.

    UPD:
    Judging by the comments, I still in vain omitted information about how this object is used inside jQuery. Comments about “made Deferred - and this is a double of such and such a method in such and such framework” or “why this Callbacks is needed - only makes the jQuery library more weighty, but doesn’t come up with real applications” - these, in my opinion, are not comments understanding the essence of the matter. Below, I want to clarify this point.

    Real use


    Callbacks is actually now used by so many jQuery 1.7+ users and was not made easy by the development team because they wanted to make a new feature. Look, the chain and logic of this question is quite simple: the

    method was implemented in the library $.ajax(), which by its nature is nothing more than an add-on for a certain Deferred - the developers improved the code, carried it separately from the main code $.ajax()(for the possibility of reuse and simplification of testing) and decided, why not publish this code (give library users access to it and document it) - it turned out $.Deferred.

    In turn $.Deferred- it was originally a two ( done()and fail()), and now three (+ moreprogress()) add-ons for Callbacks, which was made as internal code $.Deferred. And again, the developers improved and separated this code from $.Deferredby implementing the latter through $.Callbacks(by the way, the source code $.Deferredbecame much more clear and readable).

    Conclusion: the developers do not set as their main goal the addition of new “useless” features, they optimize the existing internal code, simultaneously publishing secondary, but no less useful results. And every time you use $.ajax()- know, you use $.Deferred, and therefore $.Callbacks. This is an example of real use.

    Also popular now: