1. #1
    Sencha User
    Join Date
    Jun 2007
    Posts
    46
    Vote Rating
    6
    kesteb is on a distinguished road

      2  

    Default A method for queuing Ajax requests

    A method for queuing Ajax requests


    I am using a desktop style environment. This environment can have multiple windows open, each making requests back to the server. Due to some well known issues with multiple pending Ajax requests, a queuing mechanism was deemed desirable.

    First some helper classes. This one implements a simple queue processor.

    PHP Code:
    /*
     * File: Queues.js
     * Date: 11-Sep-2012
     * By  : Kevin L. Esteb
     *
     * This module provides simple queue functionality. It supports
     * FIFO and LIFO based queues. By default it is in FIFO mode.
     *
     * The constructor can be passed the following config:
     *
     *   config = {
     *       fifo: true
     *   };
     *
     *   que = new Ext.ux.queue.Queues(config);
     *
     *   que.enqueue(value);
     *   value = que.dequeue();
     *
     * Where "fifo" can be either "true" or "false". When it is false, the
     * queue is in LIFO mode.
     *
     * ---------------------------------------------------------------------
     *
     *   Queues.js is free software: you can redistribute it and/or modify it
     *   under the terms of the GNU General Public License as published by the
     *   Free Software Foundation, either version 3 of the License, or
     *   (at your option) any later version.
     *
     *   Queues.js is distributed in the hope that it will be useful, but
     *   WITHOUT ANY WARRANTY; without even the implied warranty of
     *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     *   General Public License for more details.
     *
     *   You should have received a copy of the GNU General Public License
     *   along with RemoteStorageProvider.js. If not, see
     *   <http://www.gnu.org/licenses/>.
     *
     * ---------------------------------------------------------------------
     *
     */

    Ext.define('Ext.ux.queue.Queues', {

        
    queue: [],
        
    fifotrue,

        
    constructor: function(config) {

           
    config config || {};
           
    this.initialConfig config;

           
    Ext.apply(thisconfig);

        },

        
    enqueue: function(value) {

            
    this.queue.push(value);

        },

        
    dequeue: function() {
            var 
    value null;

            if (
    this.queue.length 0) {

                if (
    this.fifo) {

                    
    value this.queue.pop();

                } else {

                    
    value this.queue.shift();

                }

            }

            return 
    value;

        },

        
    peek: function(pos) {
            var 
    value null,
                
    last this.queue.length 1;

            if (
    this.queue.length 1) {

                if (
    pos 0) {

                    
    value this.queue[0];

                } else if (
    pos last)  {

                    
    value this.queue[last];

                } else {

                    
    value this.queue[pos];

                }

            }

            return 
    value;

        },

        
    clear: function() {

            
    this.queue = [];

        },

        
    count: function() {

            return 
    this.queue.length;

        }

    }); 
    This one implements named queues.

    PHP Code:

    /*
     * File: Manager.js
     * Date: 11-Sep-2012
     * By  : Kevin L. Esteb
     *
     * This module provides a simple queue mamanger for named queues. It is
     * implemented as a singleton. Usage is a follows:
     *
     *    qmgr = Ext.ux.queue.Manager;
     *
     *    qmgr.createQueue('ajax', {fifo: true});
     *    qmgr.addItem('ajax', value);
     *
     *    while (value = qmgr.nextItem('ajax')) {
     *
     *    }
     *
     *    qmgr.deleteQueue('ajax');
     *
     * It also exposes two events:
     *
     *    enqueued - when an item is placed into a queue.
     *    dequeued - when an item is removed from a queue.
     *
     * Both of these events provides the name of the queue that this event
     * happened on.
     *
     * ---------------------------------------------------------------------
     *
     *   Manager.js is free software: you can redistribute it and/or modify it
     *   under the terms of the GNU General Public License as published by the
     *   Free Software Foundation, either version 3 of the License, or
     *   (at your option) any later version.
     *
     *   Manager.js is distributed in the hope that it will be useful, but
     *   WITHOUT ANY WARRANTY; without even the implied warranty of
     *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     *   General Public License for more details.
     *
     *   You should have received a copy of the GNU General Public License
     *   along with RemoteStorageProvider.js. If not, see
     *   <http://www.gnu.org/licenses/>.
     *
     * ---------------------------------------------------------------------
     *
     */

    Ext.define('Ext.ux.queue.Manager', {
        
    singletontrue,
        
    mixins: {
            
    observable'Ext.util.Observable'
        
    },
        
    requires: [
            
    'Ext.ux.queue.Queues'
        
    ],
        
    uses: [
            
    'Ext.util.Observable'
        
    ],

        
    queues: [],

        
    constructor: function() {

            
    this.mixins.observable.constructor.call(this);
            
    this.addEvents(
                
    'enqueued',
                
    'dequeued'
            
    );

        },

        
    createQueue: function(nameconfig) {

            if (! 
    this.queues[name]) {

                
    this.queues[name] = Ext.create('Ext.ux.queue.Queues'config);

            }

        },

        
    deleteQueue: function(name) {
            var 
    queue null;

            if (
    queue this.getQueue(name)) {

                
    queue.clear();
                
    delete this.queues[name];

            } else {

                
    Ext.Error.raise('queue "' name '" is not defined');

            }

        },

        
    getQueue: function(name) {

            return (
    this.queues[name] || null);

        },

        
    nextItem: function(name) {
            var 
    queue nullvalue null;

            if (
    queue this.getQueue(name)) {

                
    value queue.dequeue();
                
    this.fireEvent('dequeued'name);

            } else {

                
    Ext.Error.raise('queue "' name '" is not defined');

            }

            return 
    value;

        },

        
    addItem: function(namevalue) {
            var 
    queue null;

            if (
    queue this.getQueue(name)) {

                
    queue.enqueue(value);
                
    this.fireEvent('enqueued'name);

            } else {

                
    Ext.Error.raise('queue "' name '" is not defined');

            }

        },

        
    countItems: function(name) {
            var 
    count 0queue null;

            if (
    queue this.getQueue(name)) {

                
    count queue.count();

            } else {

                
    Ext.Error.raise('queue "' name '" is not defined');

            }

            return 
    count;

        }

    }); 
    This class implements an event driven dispatcher to manage the queued requests.

    PHP Code:

    /*
     * File: Dispatcher.js
     * Date: 11-Sep-2012
     * By  : Kevin L. Esteb
     *
     * This module provides a method to perform queued ajax requests. It is
     * implemented as a singleton. Usage is as follows:
     *
     *    var config = {
     *        pending: 2,
     *        queue: {
     *            name: 'ajax',
     *            fifo: false
     *        }
     *    };
     *
     *    Ext.ux.data.Dispatcher(config).run();
     *
     * Everything after this is event driven.
     *
     * ---------------------------------------------------------------------
     *
     *   Dispatcher.js is free software: you can redistribute it and/or modify
     *   it under the terms of the GNU General Public License as published by
     *   the Free Software Foundation, either version 3 of the License, or
     *   (at your option) any later version.
     *
     *   Dispatcher.js is distributed in the hope that it will be useful, but
     *   WITHOUT ANY WARRANTY; without even the implied warranty of
     *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     *   General Public License for more details.
     *
     *   You should have received a copy of the GNU General Public License
     *   along with RemoteStorageProvider.js. If not, see
     *   <http://www.gnu.org/licenses/>.
     *
     * ---------------------------------------------------------------------
     *
     */

    Ext.define('Ext.ux.data.Dispatcher', {
        
    singletontrue,
        
    mixins: {
            
    observable'Ext.util.Observable'
        
    },
        
    requires: [
            
    'Ext.ux.queue.Queues',
            
    'Ext.ux.queue.Manager'
        
    ],
        
    uses: [
            
    'Ext.Ajax',
            
    'Ext.data.proxy.Ajax',
            
    'Ext.util.Observable',
            
    'Ext.data.Connection'
        
    ],

        
    qmgr: {},
        
    count0,
        
    pending2,
        
    queue: {
            
    name'ajax',
            
    fifofalse
        
    },

        
    constructor: function(config) {

           
    config config || {};
           
    this.initialConfig config;

           
    Ext.apply(thisconfig);

               
    this.mixins.observable.constructor.call(thisconfig);

           
    this.qmgr Ext.ux.queue.Manager;

        },

        
    run: function() {

           
    this.qmgr.createQueue(this.queue.name, {fifothis.queue.fifo});

           
    // The processing of the 'ajax' queue is event driven.
           //
           // Observe Ext.data.Connection so that we can respond to it's events.

           
    Ext.util.Observable.observe(Ext.data.Connection);
           
    Ext.data.Connection.on({
               
    requestcomplete:  {
                   
    fnthis.completed,
                   
    scopethis
               
    },
               
    requestexception: {
                   
    fnthis.exception,
                   
    scopethis
               
    }
           });

           
    // Wait for any "enqueued" events and then start processing.

           
    this.qmgr.on({
               
    enqueued: {
                   
    fnthis.dispatch,
                   
    scopethis
               
    }
           });

        },

        
    dispatch: function(name) {
            var 
    request;

            if (
    name === this.queue.name) {

                if (
    this.count this.pending) {

                    if (
    request this.qmgr.nextItem(this.queue.name)) {

                        
    Ext.Ajax.request(request);
                        
    this.count++;

                   }

                }

            }

        },

        
    completed: function() {

            
    this.count--;

            if (
    this.count 0) {

               
    this.count 0;

            }

            
    this.dispatch(this.queue.name);

        },

        
    exception: function() {

            
    this.count--;

            if (
    this.count 0) {

               
    this.count 0;

            }

            
    this.dispatch(this.queue.name);

        }

    }); 
    Now, load order is important. The above needs to be loaded and initialized before the override to Ext.data.proxy.Ajax. I found that the only reliable way to do that is to place everything into one file or use a templating engine to include them all into one request. Since the backend is written in Perl, I can use the Template Toolkit as a preprocessor. Here is the override.

    PHP Code:

    /*
     * Load order is important. This is the only way to ensure that these
     * modules are loaded before the Ext.data.proxy.Ajax override. Otherwise
     * you will get undefined name space errors.
     */

    [% INCLUDE "queue/Queues.js" %]
    [% INCLUDE 
    "queue/Manager.js" %]
    [% INCLUDE 
    "data/Dispatcher.js" %]

    /*
     * Override the default behavior of Ext.data.proxy.Ajax to use a
     * named queue to control the outstanding ajax requests. The queue
     * is processed in a LIFO manner. The "queue" and "fifo" values can
     * be overriden by configuration.
     */

    Ext.override(Ext.data.proxy.Ajax, {

        
    qmgr: {},
        
    queue'ajax',
        
    fifofalse,

        
    constructor: function(config) {

            
    config config || {};
            
    this.callParent([config]);

            
    this.qmgr Ext.ux.queue.Manager;
            
    this.qmgr.createQueue(this.queue, {fifothis.fifo});

        },

        
    doRequest: function(operationcallbackscope) {
            var 
    writer  this.getWriter(),
                
    request this.buildRequest(operationcallbackscope);

            if (
    operation.allowWrite()) {
                
    request writer.write(request);
            }

            
    Ext.apply(request, {
                
    headers       this.headers,
                
    timeout       this.timeout,
                
    scope         this,
                
    callback      this.createRequestCallback(requestoperationcallbackscope),
                
    method        this.getMethod(request),
                
    disableCachingfalse // explicitly set it to false, ServerProxy handles caching
            
    });

            
    this.qmgr.addItem(this.queuerequest);

            return 
    request;

        }

    }); 
    Now to start the processing, you need to place the following code into your applications initialization routine.

    PHP Code:

    var dispatcher Ext.ux.data.Dispatcher;
    dispatcher.run(); 
    Now every store that uses the "ajax" proxy or inherits from the 'ajax' proxy class will be queued.

  2. #2
    Sencha - Support Team scottmartin's Avatar
    Join Date
    Jul 2010
    Location
    Houston, Tx
    Posts
    9,001
    Vote Rating
    456
    scottmartin has a brilliant future scottmartin has a brilliant future scottmartin has a brilliant future scottmartin has a brilliant future scottmartin has a brilliant future scottmartin has a brilliant future scottmartin has a brilliant future scottmartin has a brilliant future scottmartin has a brilliant future scottmartin has a brilliant future scottmartin has a brilliant future

      0  

    Default


    Seems interesting .. will have to check this out. Thanks.

    Scott.

  3. #3
    Ext JS Premium Member
    Join Date
    May 2008
    Posts
    372
    Vote Rating
    4
    ttbgwt is on a distinguished road

      0  

    Default


    Hi,

    I wired this up and tested but any of my stores that has a proxy that uses POST for reads are not getting called now. Any suggestions?

    Code:
    Ext.define('XYZ.proxy.Tags', {
    
    
        extend: 'XYZ.proxy.Base',
    
    
        alias : 'proxy.proxytags',
    
    
        actionMethods:  {
            create: 'POST',
            read: 'POST',  //<== uses POST for read
            update: 'POST',
            destroy: 'POST'
        },
    
    
        url: XYZ.URL.routerUrl(),
    
    
        constructor: function (config) {
    
    
            var me = this;
    
    
            Ext.apply(me, config);
    
    
            me.callParent(arguments);
        }
    
    
    });

  4. #4
    Ext JS Premium Member
    Join Date
    May 2008
    Posts
    372
    Vote Rating
    4
    ttbgwt is on a distinguished road

      0  

    Default


    Seems to be working now. I had forgot these lines:

    var dispatcher = Ext.ux.data.Dispatcher;
    dispatcher.run();

  5. #5
    Sencha Premium Member
    Join Date
    Jan 2010
    Location
    Vancouver, Canada
    Posts
    134
    Vote Rating
    10
    bogc will become famous soon enough

      0  

    Default


    @kesteb: Did you test your code in ExtJs 4.1.x & 4.2.x?