Несколько запросов AJAX задерживают друг друга

У меня на странице длинный запрос на голосование. Сценарий на стороне сервера настроен на тайм-аут через 20 секунд.

Таким образом, когда длительный опрос «бездействует» и пользователь нажимает другую кнопку, отправка этого нового запроса откладывается до истечения времени ожидания предыдущего сценария.

Я не вижу ничего плохого в коде на стороне jQuery. Почему событие onclick задерживается?

function poll()
{
$.ajax({
    url: "/xhr/poll/1",
    data: {
        user_id: app.user.id
    },
    type: "POST",
    dataType: "JSON",
    success: pollComplete,
    error: function(response) {
        console.log(response);
    }
});
}

function pollComplete()
{
    poll();
}

function joinRoom(user_id)
{
$.ajax({
    url: "/xhr/room/join",
    dataType: "JSON",
    type: "POST",
    data: {
        user_id: app.user.id,
        room_id: room.id
    }
});
}

<button id="join" onclick="javascript:joinRoom(2);">Join</button>

############ PHP Controller on /xhr/poll

$time = time();
while ((time() - $time) < 20)
{
    $updates = $db->getNewStuff();

    foreach ($updates->getResult() as $update)
        $response[] = $update->getResponse();

    if (!empty($response))
        return $response;
    else
        usleep(1 * 1000000);

    return 'no-updates';
}

Может проблема в "усне"?

Снимок экрана XHR


person F.P    schedule 01.08.2011    source источник
comment
проблема есть и в локальном хосте??   -  person Praveen Prasad    schedule 04.08.2011
comment
Использует ли PHP-код для вызова AJAX сеансы?   -  person Chris Baker    schedule 04.08.2011


Ответы (3)


Если вы используете сеансы в функциях обработки AJAX, вы можете столкнуться с проблемой, когда сервер поддерживает данные сеанса на диске. Если это так, данные могут быть заблокированы первым запросом, поэтому каждый последующий запрос заканчивается ожиданием доступности файла данных сеанса, прежде чем он продолжится. По сути, это заставляет асинхронные вызовы блокировать друг друга, вы получаете линейные ответы на запросы в хронологическом порядке - синхронные. (вот справочная статья)

Специфическое для PHP решение состоит в том, чтобы использовать session_write_close (документы), чтобы закрыть сеанс, как только вы не нужно больше. Это позволяет выполнять другие последующие запросы, поскольку данные сеанса будут «разблокированы». Другие серверные языки управляют сессиями по-разному, но обычно это то, чем вы можете управлять или контролировать с помощью какого-то механизма.

Управление сеансами может иметь некоторые подводные камни. Если вы вызовете session_write_close (или иным образом завершите сеанс) прямо перед тем, как вернуть ответ, вы не сделаете себе никаких одолжений, потому что сеанс будет разблокирован, как только ответ будет отправлен. Таким образом, его нужно вызывать как можно раньше. В небольших проектах это не так уж плохо, потому что у вас часто есть php-скрипт, который просто обрабатывает запрос и выводит ответ, но если у вас есть более крупный фреймворк и ваш обработчик запросов является лишь частью этого, вам придется изучить решение более высокого уровня для неблокирующего использования сеанса, чтобы ваши подкомпоненты не закрывали сеанс, который, как ожидает инфраструктура, все еще открыт.

Один маршрут - пойти с сеансом базы данных. У этого решения есть плюсы и минусы, которые выходят за рамки этого ответа - проверьте Google для исчерпывающего обсуждения вашего конкретного языка на стороне сервера. Другой способ — использовать функцию, которая открывает сеанс, добавляет переменную, а затем закрывает его. С этим решением вы рискуете попасть в условия гонки, но вот грубый план с использованием PHP в качестве примера:

function get_session_var($key, $default=null) {
    if (strlen($key) < 1)
        return null;
    if (!isset($_SESSION) || !is_array($_SESSION)) {
        session_start();
        session_write_close();
    }
    if (array_key_exists($key, $_SESSION))
        return $_SESSION[$key];
    return $default;
}
function set_session_var($key, $value=null) {
    if (strlen($key) < 1)
        return false;
    if ($value === null && array_key_exists($key, $_SESSION)) {
        session_start();
        unset($_SESSION[$key]);
    } elseif ($value != null) {
        session_start();
        $_SESSION[$key] = $value;
    } else {
        return false;
    }
    session_write_close();
    return true;
}
person Chris Baker    schedule 03.08.2011
comment
Я не совсем использовал предложенное вами решение, но подсказка с session_write_close() была идеальной! Мои запросы использовали сеансы, и их отключение работало отлично. - person F.P; 04.08.2011

Это звучит в соответствии с правилом 2-х запросов — браузеры разрешают только два одновременных подключения к одному и тому же хосту в любой момент времени. При этом вы должны быть в порядке с длинным каналом опроса (получения) и отправки. Вы запускаете длинный опрос после загрузки страницы с помощью $(function(){...? Вы уверены, что запрос задерживается на клиенте, а не в браузере? Что вы видите в firebug?

person Justin Beckwith    schedule 01.08.2011
comment
Да, я уверен, потому что запрос даже не отображается в firebug в течение нескольких секунд. Но когда опрос заканчивается и начинается сначала, появляется запрос, как на скриншоте выше. - person F.P; 02.08.2011
comment
Я протестировал только AJAX, без опроса, и это заняло около 800 мс. Когда опрос запущен, он всегда занимает более 2 секунд. - person F.P; 02.08.2011
comment
Я согласен здесь. Хотя новые браузеры позволяют больше (я думаю, FF позволяет 6, а IE 8 позволяет 8). - person Mrchief; 04.08.2011
comment
Как уже упоминалось (вроде), эта проблема в основном связана с IE 8 и более ранними версиями (weblogs.asp.net/mschwarz/archive/2008/07/21/) Однако, как указано в ссылке, спецификация HTTP предполагает, что клиенты должны ограничить количество одновременных подключений. Вот почему предлагается CDN — CDN — это другое доменное имя, которое позволяет обойти ограничения параллелизма пользовательских агентов. Все сказанное, я не думаю, что это полностью проблема ОП :) - person Chris Baker; 04.08.2011

Одна вещь, которую вы можете сделать, вы можете прервать текущий опрос и сначала запустить свой запрос, а затем снова начать опрос.

//make sure pollJqXhr.abort is not undefined
var pollJqXhr={abort:$.noop}; 

function poll()
{
    //assign actual jqXhr object
    pollJqXhr=jQuery.ajax({youroptions});
}

function pollComplete()
{
   poll();
}


function joinRoom(user_id)
{
   //pause polling
   pollJqXhr.abort();

   jquery.ajax({
           /*options here*/
           success:function()
           {
                /*Your codes*/

                //restart poll
                poll()
           }
    });
}
person Praveen Prasad    schedule 03.08.2011
comment
Похоже, это лечение симптома, а не причины - person Chris Baker; 04.08.2011