Обновление информации в фоне Revisited, или long polling для ретрограда

в 20:48, , рубрики: ajax, javascript, jquery, json, polling, rss, метки: , , ,

Обновление информации в фоне Revisited, или long polling для ретрограда

Привет! В данном посте я хочу поделиться своим вариантом использования схемы обновления данных, описанной в статье читателя zim32, в одном простеньком вебдванольном приложении.

Disclaimer: Да не введет вас в заблуждение наличие термина «long polling» в заголовке. Это всего лишь присказка, а сказ о другом.

Суть задачи у меня почти такая же как и в исходном посте: мы находимся на странице вывода фильтра RSS-лент, необходимо обновлять ее содержимое по мере поступления новых данных.

БД у меня не используется. Все новые данные сразу пишутся в файловый кэш в формате JSON. Одновременно с этим создается пустой файл-маркер, отдельный для каждого потока (получателя) данных.

В остальном все то же самое, что и в исходном посте. Клиент (браузер) посылает в цикле запросы к файлу-маркеру. При обнаружении изменения в http-заголовке Etag (или Last-Modified) делается запрос на получение самих данных из JSON кэша. Для снижения активности клиента в то время, когда наша страница не в фокусе, добавляем обработку событий onblur и onfocus объекта window.

В результате часть сценария страницы, отвечающая за обновление данных, выглядит примерно так:

Код Javascript/jQuery

$(function() {
    var B_AJAXCACHE = false;
    var B_STAMP_ETAG = true;
    var urlResult = ''; /* URL JSON-файла данных */
    var urlResultStamp = ''; /* URL файла-маркера данных */
    var nItems = 0; /* счетчик данных */
    var sStampResult = 0; /* последний таймстемп файла-маркера */
    var nIntPolling = 120 * 1000; /* интервал между запросами файла-маркера */
    var nIdTimer = 0;
    var bFocus = true; /* признак нахождения окна стр-цы в фокусе */
    var bFocusUpd = false; /* признак появления новых данных при получении окном фокуса */
    var nStateSound = 0; /* признак использования звукового уведомления о новых данных */

    ...

    queryPolling();

    $(window).focus(function() {
        var b = bFocus, b2 = bFocusUpd;
        bFocus = true; bFocusUpd = false;
        if (b || !b2) return;
        nStateSound? queryPolling() : postponePolling();
    });

    $(window).blur(function() {
        bFocus = false;
        bFocusUpd = nStateSound? false : Boolean(nIdTimer);
        if (!nStateSound && bFocusUpd) stopPolling();
    });

    /**
     * Рекурсивный Ajax запрос файла-маркера
     */
    function queryPolling() {
        stopPolling();
        if (!urlResultStamp) return;
        var bOk = false;
        queryStaticEx(urlResultStamp, 'text', sStampResult, function(txt, s) {
            if (s == sStampResult) return;
            if (!bFocus) {
                bFocusUpd = true; playSound();
                return;
            }
            sStampResult = s;
            bOk = true;
            queryResults();
        })
        .complete(function() {
            if (bOk) return;
            postponePolling();
        });
    }

    /**
     * Отложенный Ajax запрос файла-маркера
     */
    function postponePolling() {
        nIdTimer= setTimeout(queryPolling, nIntPolling);
    }

    /**
     * Остановка рекурсивных запросов файла-маркера
     */
    function stopPolling() {
        if (nIdTimer) clearTimeout(nIdTimer); nIdTimer = 0;
    }

    /**
     * Ajax запрос JSON-данных
     */
    function queryResults() {
        if (!urlResult) return;
        var nPrev = nItems;
        queryStatic(urlResult, outResults)
        .complete(function() {
            if (nItems != nPrev) playSound();
            postponePolling();
        });
    }

    /**
     * Ajax запрос статического ресурса
     * @param sUrl - URL ресурса
     * @param fnSuccess - функция, вызываемый при успехе запроса
     * @return объект Deferred
     */
    function queryStatic(sUrl, fnSuccess) {
        return $.ajax({
            url: sUrl,
            type: 'GET',
            dataType: 'json',
            cache: B_AJAXCACHE,
            success: fnSuccess
        });
    }

    /**
     * Ajax запрос таймстемпа статического ресурса
     * @param sUrl - URL ресурса
     * @param sType - тип ресурса
     * @param sStamp - последний таймстемп ресурса
     * @param fnSuccess - функция, вызываемый при успехе запроса
     * @return объект Deferred
     */
    function queryStaticEx(sUrl, sType, sStamp, fnSuccess) {
        return $.ajax({
            url: sUrl,
            type: 'GET',
            dataType: sType,
            cache: B_AJAXCACHE,
            beforeSend: function(oXhr) {
                if (B_STAMP_ETAG && sStamp != '')
                    oXhr.setRequestHeader('If-None-Match', sStamp);
            },
            success: function(data, nStatus, oXhr) {
                var s = B_STAMP_ETAG? getStampEtag(oXhr) : getStampStd(oXhr);
                if (s) fnSuccess(data, s);
            }
        });
    }

    /**
     * Получение таймстемпа ресурса из заголовка Last-Modified
     * @param oXhr - Xhr объект
     */
    function getStampStd(oXhr) {
        if (!oXhr) return 0;
        var s = oXhr.getResponseHeader('Last-Modified');
        return s? Date.parse(s) : 0;
    }

    /**
     * Получение таймстемпа ресурса из заголовка ETag
     * @param oXhr - Xhr объект
     */
    function getStampEtag(oXhr) {
        if (!oXhr) return 0;
        var s = oXhr.getResponseHeader('ETag');
        return s? s : '';
    }

    /**
     * Вывод JSON-данных
     * @param aData - массив данных
     */
    function outResults(aData) {
        ...
    }

    /**
     * Звуковое уведомление о новых данных
     */
    function playSound() {
        ...
    }

    ...

});

Полный код приложения, а также демо, находятся здесь. Писал я его исключительно для себя, но буду рад, если оно пригодится кому-нибудь еще.

Спасибо за внимание.

Автор: xmeoff

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js