Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

jQuery Long Polling (With PHP server side)

I'm having a bit of a nightmare here, so any help would be gratefully appreciated! Firstly, I'll explain what I'm trying to do:

I'm trying to implement a system like described here: https://stackoverflow.com/a/1086448/1034392 on my localhost MAMP server using Yii framework. I have a function that checks if there are any new notifications in the DB - if so, it parses them and json encodes them. I have this function called on a while loop every 5 secs.

So: going to /user/unreadNotifications triggers the following

    Yii::log('test'); // to check it's getting called  

    $this->layout=false;

    header('Content-Type: application/json');

    // LONG POLLING 
    while (Yii::app()->user->getNotifications() == null) {
        sleep(5);
    }

    echo Yii::app()->user->getNotifications(); // prints out json if new notification

    Yii::app()->end();

    return;

This works fine - gone to the link in browser and verified json response - all good.

I have then tried all sorts of jQuery stuff to get it working... The ONLY way I have found to work is using $.ajax with type POST but ONLY when there is a waiting notification (so some json is returned). $.get or $.post gets "aborted" (displayed in firebug) but the URL is called (because I can see the log file is updated) - odd.

My original setup using $.get is:

        <script type="text/javascript">
            function notificationPoll() {
                $.get('<?php echo Yii::app()->createUrl('user/unreadNotifications') ?>','', function(result) {
                    $.each(result.events, function(events) {
                        alert('New Notification!');
                    });
                    notificationPoll();
                }, 'json');
            }
        </script>

        <script type="text/javascript">
            $(document).ready(function() {
                $.ajaxSetup({
                    timeout: 60 //set a global ajax timeout of a minute
                });
                notificationPoll();
            });
        </script>

This just gets "aborted" for some reason. I've tried with 'jsonp' even though it is not a CORS request.. but that doesn't work either.

Can't seem to get anywhere with this! Can anyone chip in?

Many thanks

like image 311
cud_programmer Avatar asked Jul 02 '12 15:07

cud_programmer


4 Answers

You must be sure that the function terminates inside a reasonable time. What you could do is this:

$ttl = 10;
while ($ttl--) {
    $json = Yii::app()->user->getNotifications();
    if (null != $json) {
        break;
    }
    sleep(1);
}
if (null == $json) {
    $json = json_encode(array(
        'nothing' => true
    ));
}
header('Content-Type: application/json');
echo $json;

Yii::app()->end();
return;

You set up the polling function as a timer using setInterval(). The function will now be called every, say, 10 seconds, and you may need to set up a semaphore to avoid it being called before the previous iteration has returned:

var timer = setInterval(
    function() {
        if (this.calling) {
            return;
        }
        var fn = this;
        fn.calling = true;
        $.post(url)
         .done(function(data) {
            ..
         })
         .always(function() {
            fn.calling = false;
         });
    },
    10000
);

Then the polling function in AJAX needs to check (in the .done()) callback) that the notification is there:

function(data) {
    if (data.hasOwnProperty('nothing')) {
        alert('No notifications');
        return;
    }
    console.log(data);
    ...
}

Now one important thing is what does your notification look like. Here I've assumed it is a JSON encoded string. But if it is an array or object that the Yii function returns instead, you need to handle its encoding. This might be even cleaner, without any IF's:

header('Content-Type: ...
die(json_encode(
    array(
        'status'         => 'success',
        'notification'   => $json /* This is NULL or an array */
    )
    // Javascript side we check that data.notification is not null.
));

The decoding is already handled by jQuery, so the variable "data" above will already be a Javascript object, and you need not call JSON.parse. You can check that data is an object though, and that it has the expected properties. That will warn you of any errors.

To handle the navigation to another page, you can store the setInterval()-supplied timer ID of the polling Javascript function in a global variable, and delete the timer when the page calls onUnload() to deactivate the polling.

like image 193
LSerni Avatar answered Nov 18 '22 06:11

LSerni


What does the getNotifications return if there are no notifications? jQuery expects an JSON format to be returned but when you just echo an empty string the request fails as the format of the response is not a JSON. Make sure to echo JSON string everytime.

like image 38
Andy Avatar answered Nov 18 '22 04:11

Andy


How about this. I assume the $.(get) is in a function called notificationPoll(); which is re-called once completed.

$.ajax({
    url: event_feed_href,
    async: false,
    timeout: 60000,
    done: function(data) {
            var got_json=false;
            try {
                var json = JSON.parse(data);
                got_json=true;
            }
            catch(e) {
                // failed to return JSON data
                alert('Wierd!');
            } 
            if(got_json) {
                // process json data
                alert('New Notification!');
            }
        },
    always: function() {
            notificationPoll();
        }
    });

I've used done and always here as jQuery says is depreciating success: fail:

like image 38
Waygood Avatar answered Nov 18 '22 06:11

Waygood


I am interested in the code aborting and there may be your answer. It can happen due to multiple reasons

  • Query.get failing. Ajax call does not work for any reasons. Parsing error.
  • Bad javascript syntax. For example: the code in the success handler expects the events property in the input argument which may not be true.

For jQuery.get, as suggested in the jQuery.get (https://api.jquery.com/jQuery.get/) documentation, the function can silently failing, I will suggest to use .ajaxError to see if the get is failing.

If a request with jQuery.get() returns an error code, it will fail silently unless the script has also called the global .ajaxError() method. Alternatively, as of jQuery 1.5, the .error() method of the jqXHR object returned by jQuery.get() is also available for error handling.

For javascript syntax error, I will suggest to step through the firebug debugger. Add a breakpoint at the first line in the success handler and then step each line from there. It is tedious, but it works.

like image 1
Jay Rajput Avatar answered Nov 18 '22 05:11

Jay Rajput