function sendTalks(talks, response) {
respondJSON(response, 200, {
serverTime: Date.now(),
talks: talks
});
}
Обработчик должен посмотреть на все параметры запроса в его URL, чтобы проверить, не задан ли параметр changesSince
. Если дать функции parse
модуля “url”
второй аргумент значения true
, он также распарсит вторую часть URL – query
, часть запроса. У возвращаемого объекта будет свойство query
, в котором будет ещё один объект, с именами и значениями параметров.
router.add("GET", /^\/talks$/, function(request, response) {
var query = require("url").parse(request.url, true).query;
if (query.changesSince == null) {
var list = [];
for (var title in talks)
list.push(talks[title]);
sendTalks(list, response);
} else {
var since = Number(query.changesSince);
if (isNaN(since)) {
respond(response, 400, "Invalid parameter");
} else {
var changed = getChangedTalks(since);
if (changed.length > 0)
sendTalks(changed, response);
else
waitForChanges(since, response);
}
}
});
При отсутствии параметра changesSince
обработчик просто строит список всех тем и возвращает его.
Иначе, сперва надо проверить параметр changeSince
на предмет того, что это число. Функция getChangedTalks
, которую мы вскоре определим, возвращает массив изменённых тем с некоего заданного времени. Если она возвращает пустой массив, то серверу нечего возвращать клиенту, так что он сохраняет объект response
(при помощи waitForChanges
), чтобы ответить попозже.
var waiting = [];
function waitForChanges(since, response) {
var waiter = {since: since, response: response};
waiting.push(waiter);
setTimeout(function() {
var found = waiting.indexOf(waiter);
if (found > -1) {
waiting.splice(found, 1);
sendTalks([], response);
}
}, 90 * 1000);
}
Метод splice
используется для вырезания куска массива. Ему задаётся индекс и количество элементов, и он изменяет массив, удаляя это количество элементов после заданного индекса. В этом случае мы удаляем один элемент – объект, ждущий ответ, чей индекс мы узнали через indexOf
. Если вы передадите дополнительные аргументы в splice
, их значения будут вставлены в массив на заданной позиции, и заместят удалённые элементы.
Когда объект response
сохранён в массиве waiting
, задаётся таймаут. После 90 секунд он проверяет, ждёт ли ещё запрос, и если да – отправляет пустой ответ и удаляет его из массива waiting
.
Чтобы найти именно те темы, которые сменились после заданного времени, нам надо отслеживать историю изменений. Регистрация изменения при помощи registerChange
запомнит это изменение, вместе с текущим временем, в массиве changes
. Когда случается изменение, это значит – есть новые данные, поэтому всем ждущим запросам можно немедленно ответить.
var changes = [];
function registerChange(title) {
changes.push({title: title, time: Date.now()});
waiting.forEach(function(waiter) {
sendTalks(getChangedTalks(waiter.since), waiter.response);
});
waiting = [];
}
Наконец, getChangedTalks
использует массив changes
, чтобы построить массив изменившихся тем, включая объекты со свойством deleted
для тем, которых уже не существует. При построении массива getChangedTalks
должна убедиться, что одна и та же тема не включается дважды, так как тема могла измениться несколько раз с заданного момента времени.
function getChangedTalks(since) {
var found = [];
function alreadySeen(title) {
return found.some(function(f) {return f.title == title;});
}
for (var i = changes.length - 1; i >= 0; i--) {
var change = changes[i];
if (change.time <= since)
break;
else if (alreadySeen(change.title))
continue;
else if (change.title in talks)
found.push(talks[change.title]);
else
found.push({title: change.title, deleted: true});
}
return found;
}
Вот и всё с кодом сервера. Запуск написанного кода даст вам сервер, работающий на порту 8000, который выдаёт файлы из публичной поддиректории и управляет интерфейсом тем по адресу /talks
.
Клиент
Клиентская часть веб-сайта по управлению темами состоит из трёх файлов: HTML-страница, таблица стилей и файл JavaScript.