jQuery长轮询(带有PHP服务器端)

8
我在这里遇到一些麻烦,希望您能提供帮助!首先,我将解释我要做的事情:
我正在尝试实现一个像这里描述的系统:https://dev59.com/wXNA5IYBdhLWcg3wHp-B#1086448,使用 Yii 框架在我的本地 MAMP 服务器上。 我有一个函数来检查 DB 中是否有任何新的通知 - 如果是,则解析它们并进行 JSON 编码。 我在 while 循环中每 5 秒钟调用此函数。
所以:访问 /user/unreadNotifications 会触发以下操作
    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;

这很好用-在浏览器中访问链接并验证JSON响应,一切正常。然后我尝试了各种jQuery方法使其工作……我发现唯一可行的方法是使用$.ajax进行POST请求,但仅当等待通知时(因此会返回一些JSON)。使用$.get$.post会被“abort”(在firebug中显示),但URL已被调用(因为我可以看到日志文件已更新)-奇怪。我的最初设置使用$.get
        <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>

由于某些原因,这个请求被“中止”了。尽管它不是CORS请求,但我已经尝试使用“jsonp”,但也没有用。

似乎无法解决这个问题!有谁能提供帮助吗?

非常感谢


忘记提到了:当我使用$ .ajax与POST类型,并且没有通知要显示时,任何一个具有jQuery代码的网站页面都不会加载,直到收到通知。 - cud_programmer
控制台日志错误??粘贴它。。 - user6079755
4个回答

3

您必须确保函数在合理的时间内终止。您可以这样做:

$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;

使用 setInterval() 函数将轮询功能设置为计时器。现在这个函数将每隔10秒调用一次,你可能需要设置一个信号量来避免在上一次迭代返回之前被调用:

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
);

然后,在 AJAX 中的轮询函数需要检查(在 .done() 回调中),通知是否存在:

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

现在重要的一点是你的通知长什么样。在这里,我假设它是一个JSON编码的字符串。但如果它是Yii函数返回的数组或对象,则需要处理其编码。这可能会更加简洁,没有任何IF语句:

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.
));

解码已经被jQuery处理,所以上面的变量"data"已经是JavaScript对象了,你不需要调用JSON.parse。你可以检查data确实是一个对象,并且它具有预期的属性。这将警告你任何错误。
为了处理导航到另一个页面,你可以将轮询Javascript函数提供的setInterval()定时器ID存储在全局变量中,并在页面调用onUnload()以停用轮询时删除该定时器。

2
如果没有通知,getNotifications会返回什么?jQuery期望返回一个JSON格式,但当您只回显空字符串时,请求会失败,因为响应格式不是JSON。 请确保始终回显JSON字符串。

谢谢Andy。目前,如果没有通知,getNotifications()会返回null,因此用户/未读通知只是循环,直到有新的通知才会实际返回任何内容。 - cud_programmer
它应该只是超时,没有响应。也许更好的$.ajax结构会更容易调试,包括个人超时、成功、try{解析json,数据可用}catch(e){},数据可用:处理JSON数据,失败。 - Waygood
好了-最终看起来我已经缩小了范围! 当我调用$ .ajaxSteup()并设置timeout时...它在毫秒中设置超时? 我将其增加到60000(60秒),现在$ .get就不会“中止”! 不过还有一个问题-初始加载页面很好,但是导航到另一个页面就会卡住。 有没有办法在离开页面时关闭处理GET请求? (我想这可能会导致下一页无法加载..) - cud_programmer
为服务脚本添加最大循环计数器或调整超时时间。 - Waygood
@Andy 你说得对,超时时间太短了。而且,有一种方法可以中止 jQuery 的 ajax 请求:var request = $.get(...); request.abort(); - Ast Derek

2
这样怎么样。我假设$.(get)在名为notificationPoll()的函数中,该函数在完成后被重新调用。
$.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();
        }
    });

我使用了done和always,因为jQuery表示success: fail:已经过时。


长轮询必须没有客户端超时。超时只能用于网络错误和重试连接。 - Deep

1

我对代码中的异常情况很感兴趣,也许你可以在这里找到答案。它可能会由多种原因引起。

  • Query.get失败。 Ajax调用由于任何原因而不起作用。解析错误。
  • 错误的javascript语法。例如:成功处理程序中的代码期望输入参数中的事件属性,这可能不是真实的。

对于jQuery.get,建议按照jQuery.get(https://api.jquery.com/jQuery.get/)文档中提供的方法,使用.ajaxError来查看是否出现故障。

如果使用jQuery.get()的请求返回错误代码,则除非脚本还调用了全局.ajaxError()方法,否则它将悄悄地失败。或者,从jQuery 1.5开始,还可用于错误处理的jqXHR对象的.error()方法也可用。

对于javascript语法错误,建议使用Firebug调试器逐步进行。在成功处理程序的第一行添加断点,然后从那里逐行步进。虽然有些繁琐,但它确实有效。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接