jQuery Long Polling (With PHP server side)
Asked Answered
O

4

8

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://mcmap.net/q/107917/-how-does-facebook-gmail-send-the-real-time-notification 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

Oliva answered 2/7, 2012 at 15:50 Comment(2)
Forgot to mention: when I use $.ajax with POST type and there are NO notifications to be displayed, any site page that has the jQuery code on just doesn't load until a notification comes through.Oliva
Console log error?? paste it..Selfinsurance
I
3

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.

Impatient answered 23/10, 2016 at 12:2 Comment(0)
S
2

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.

Safko answered 2/7, 2012 at 16:12 Comment(5)
thanks Andy. Currently getNotifications() returns null if there are no notifications so user/unreadNotifications just loops and doesn't actually return anything until there is a new notification.Oliva
it should just timeout, with no response. Perhaps a better $.ajax structure would be easier to debug, with personal timeout, success, try{parse json, data_available }catch(e) { }, data_available:process JSON data, failSegregate
Ok - looks like I've narrowed it down finally! When I call $.ajaxSteup() and set timeout.. it's setting timeout in milliseconds? I've upped that to 60000 (60 secs) and now the $.get doesn't 'abort'! One last problem though - loading a page initially is fine, however navigating to another page just gets stuck. Is there a way of shutting down the processing GET request on leaving page? (I guess that is causing the next page not to load..)Oliva
add a max loop counter or adjust timeout for the serving scriptSegregate
@Safko You are right, the timeout was too short. And yes, there is a way to abort jQuery ajax requests: var request = $.get(...); request.abort();Billetdoux
S
2

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:

Segregate answered 2/7, 2012 at 16:34 Comment(1)
Long polling must be without client timeout. Timeout can be use only for network errors and retry connect.Proclamation
M
1

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.

Madea answered 29/10, 2016 at 2:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.