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://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

Community
  • 1
  • 1
cud_programmer
  • 1,244
  • 1
  • 20
  • 37
  • 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. – cud_programmer Jul 02 '12 at 15:52
  • Console log error?? paste it.. –  Oct 24 '16 at 16:15

4 Answers4

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.

LSerni
  • 55,617
  • 10
  • 65
  • 107
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.

Andy
  • 128
  • 1
  • 6
  • 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. – cud_programmer Jul 02 '12 at 16:18
  • 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, fail – Waygood Jul 02 '12 at 16:19
  • 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..) – cud_programmer Jul 02 '12 at 16:31
  • add a max loop counter or adjust timeout for the serving script – Waygood Jul 02 '12 at 16:36
  • @Andy You are right, the timeout was too short. And yes, there is a way to abort jQuery ajax requests: `var request = $.get(...); request.abort();` – Ast Derek Oct 25 '16 at 03:20
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:

Waygood
  • 2,657
  • 2
  • 15
  • 16
  • Long polling must be without client timeout. Timeout can be use only for network errors and retry connect. – Deep Oct 28 '16 at 05:27
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.

Jay Rajput
  • 1,813
  • 17
  • 23