Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
313 changes: 154 additions & 159 deletions Pilot.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
});
})(typeof define === 'function' && define.amd ? define : function (deps, callback) {
window.Pilot = callback(window.Emitter);
}, function (define) {define('src/querystring',[], function () {
}, function (define) {
define('src/querystring',[], function () {
'use strict';

var encodeURIComponent = window.encodeURIComponent;
Expand Down Expand Up @@ -476,133 +477,7 @@ define('src/match',[], function () {
};
});

define('src/action-queue',['Emitter'], function(Emitter) {

function ActionQueue() {
Emitter.apply(this);

this._queue = [];
this._activeIds = {};
this._id = 0;
this._lastQueueItem = void 0;
this._endedCount = -1;
}

ActionQueue.PRIORITY_HIGH = 1;
ActionQueue.PRIORITY_LOW = 0;

ActionQueue.prototype = {
constructor: ActionQueue,

push: function(request, action) {
// TODO: arg types check

// Проставляем по умолчанию наивысший приоритет
if (action.priority == null) {
action.priority = ActionQueue.PRIORITY_HIGH;
}

var queueItem = {
request: request,
action: action,
timestamp: Date.now(),
id: this._id++
};

// Добавляем в очередь
this._queue.push(queueItem);
// Возвращаем уникальный id
return queueItem.id;
},

remove: function(id) {
// Если query был в _activeIds
if (this._activeIds[id]) {
// Сбросим _lastQueueItem
if (this._lastQueueItem === this._activeIds[id]) {
this._lastQueueItem = void 0;
}

// Сообщим, что прекратили выполнять этот экшн
this.notifyEnd(id, void 0);
return;
}

var nextQueue = [];

// Формируем новую очередь без экшна с указанным id
for (var i = 0; i < this._queue.length; i++) {
if (this._queue[i].id !== id) {
nextQueue.push(this._queue[i]);
}
}

// Сохраним новую очередь
this._queue = nextQueue;
// Сообщим, что прекратили выполнять этот экшн
this.notifyEnd(id, void 0);
},

canPoll: function() {
var nextItem = this._queue[0];
var lastActiveItem = this._lastQueueItem;

// Не можем поллить, так как очередь пуста
if (!nextItem) {
return false;
}

// Можем поллить, так как ничего не запущено
if (!lastActiveItem) {
return true;
}

// Можем поллить, если приоритет последнего запущенного экшна равен приоритету следующего экшна в очереди
return lastActiveItem.action.priority === nextItem.action.priority;
},

poll: function() {
var queueItem = this._queue.shift();

this._activeIds[queueItem.id] = queueItem;
this._lastQueueItem = queueItem;

return queueItem;
},

notifyEnd: function(id, result) {
// Сбрасываем lastQueueItem, если закончили именно его
if (this._lastQueueItem === this._activeIds[id]) {
this._lastQueueItem = void 0;
}

// Удаляем из активных в любом случае
delete this._activeIds[id];
// Сообщаем Loader
this.emit(id + ':end', result);

// Увеличиваем счётчик завершённых экшнов
this._endedCount++;
},

awaitEnd: function(id) {
// Если экшн уже давно выполнился
if (id <= this._endedCount) {
return Promise.resolve();
}

// Ожидаем выполнения экшна
return new Promise(function(resolve) {
this.one(id + ':end', resolve);
}.bind(this));
},
};

return ActionQueue;

});

define('src/loader',['./match', './action-queue'], function (match, ActionQueue) {
define('src/loader',['./match'], function (match, Emitter) {
'use strict';

var _cast = function (name, model) {
Expand Down Expand Up @@ -652,10 +527,16 @@ define('src/loader',['./match', './action-queue'], function (match, ActionQueue)

// Инкрементивный ID запросов нужен для performance
this._lastReqId = 0;
// Счётчик выполняемых запросов с высоким приоритетом
// Запросы с низким приоритетом будут выполняться только после того, как этот счётчик станет 0
this._highPriorityQueries = 0;
// Если есть запросы с высоким приоритетом, этот промис разрезолвится после завершения последнего запроса
this._highPriorityPromise = null;
this._highPriorityPromiseResolve = null;
// Приоритет последнего экшна
this._lastPriority = Loader.PRIORITY_LOW;
// Дебаг-режим, выводит в performance все экшны
this._debug = false;
// Очередь экшнов
this._actionQueue = new ActionQueue();

this.names.forEach(function (name) {
this._index[name] = _cast(name, models[name]);
Expand Down Expand Up @@ -840,34 +721,68 @@ define('src/loader',['./match', './action-queue'], function (match, ActionQueue)
return _fetchPromises[_persistKey];
}

// Добавляем экшн в очередь
var actionId = _this._actionQueue.push(_req, action);
// Пробуем выполнить следующий экшн из очереди
this._tryProcessQueue();
// Возвращаем промис, который выполнится, когда выполнится этот экшн
return _this._actionQueue.awaitEnd(actionId);
},
// Приоритет действия
var priority = action.priority == null ? Loader.PRIORITY_HIGH : action.priority;

if (
_this._highPriorityQueries &&
(priority !== _this._lastPriority || priority === Loader.PRIORITY_LOW)
) {
return _this._highPriorityPromise
.then(function() {
// Попробуем сделать действие ещё раз после выполнения всех действий с более высоким приоритетом
return _this._executeActionAsync(req, action);
});
}

_tryProcessQueue: function() {
while (this._actionQueue.canPoll()) {
var queueItem = this._actionQueue.poll();
// Выставляем активный приоритет
_this._highPriorityQueries++;
_this._lastPriority = priority;

// Отправляем экшн выполняться
var actionPromise = this._loadSources(queueItem.request, queueItem.action);
if (!_this._highPriorityPromise) {
_this._highPriorityPromise = Promise.resolve();
}

actionPromise
// Ошибку на этом этапе уже обработали
.catch(function () {
})
.then(function (queueItem, result) {
// Сообщаем, что экшн прекратили выполнять
this._actionQueue.notifyEnd(queueItem.id, result);
// Пробуем выполнить следующий экшн
this._tryProcessQueue();
}.bind(this, queueItem));
_this._highPriorityPromise = _this._highPriorityPromise.then(
new Promise(function (resolve) {
_this._highPriorityPromiseResolve = resolve;
})
);

// Отправляем экшн выполняться
var actionPromise = this._loadSources(_req, action);

actionPromise
// Ошибку на этом этапе уже обработали
.catch(function () {})
.then(function () {
_this._handleActionEnd();
});

return actionPromise;
},

_handleActionEnd: function() {
var _this = this;
_this._highPriorityQueries--;

// Резолвим high priority promise, если закончили выполнять экшн с высоким приоритетом
if (!_this._highPriorityQueries) {
_this._highPriorityPromiseResolve();
_this._highPriorityPromise = null;
}
},

_executeActionAsync: function(req, action) {
var _this = this;

return new Promise(function (resolve) {
setImmediate(function () {
resolve(_this._executeAction(req, action));
});
});
},


_measurePerformance: function (measureName) {
if (this._debug && window.performance) {
Expand Down Expand Up @@ -925,8 +840,8 @@ define('src/loader',['./match', './action-queue'], function (match, ActionQueue)

Loader.ACTION_NAVIGATE = 'NAVIGATE';
Loader.ACTION_NONE = 'NONE';
Loader.PRIORITY_LOW = ActionQueue.PRIORITY_LOW;
Loader.PRIORITY_HIGH = ActionQueue.PRIORITY_HIGH;
Loader.PRIORITY_LOW = 0;
Loader.PRIORITY_HIGH = 1;

// Export
return Loader;
Expand Down Expand Up @@ -964,6 +879,9 @@ define('src/request',['./url', './querystring'], function (/** URL */URL, /** qu
this.router = router;
this.referrer = referrer;
this.redirectHref = null;

// Алиас, который сматчился
this.alias = void 0;
};


Expand Down Expand Up @@ -1047,6 +965,27 @@ define('src/route',[
return url.replace(/\/+$/, '/');
};

/**
* Кладёт в target правила из source, если их там не было
*/
var _mergeRules = function (source, target) {
if (!source) {
source = {};
}

if (!target) {
target = {};
}

Object.keys(source).forEach(function (sourceRule) {
if (!target[sourceRule]) {
target[sourceRule] = source[sourceRule];
}
});

return target;
};


/**
* Преобразование образца маршрута в функцию генерации URL
Expand Down Expand Up @@ -1165,9 +1104,40 @@ define('src/route',[
*/
this.model = options.model.defaults();

// Основной URL маршрута
this.url.regexp = Url.toMatcher(this.url.pattern + (options.__group__ ? '/:any([a-z0-9\\/-]*)' : ''));
this._urlBuilder = _toUrlBuilder(this.url.pattern);

// Алиасы
options.aliases = options.aliases || {};

this.aliases = Object.keys(options.aliases).map(function (name) {
var url = options.aliases[name];

if (typeof url === 'string') {
url = {pattern: url};
}

// Какой-то неправильный URL передали
if (!url || typeof url !== 'object') {
return function () {
return false
};
}

// Собираем всё, что нужно для построения URL по алиасу и наоборот
url.regexp = Url.toMatcher(url.pattern);

url.params = _mergeRules(this.url.params, url.params);
url.query = _mergeRules(this.url.query, url.query);

return {
// Делаем функцию, которая будет смотреть совпадения этого урла с любым другим
matcher: this._matchWithAnyUrl.bind(this, url),
name: name
};
}.bind(this));

// Родительский маршрут (группа)
this.parentRoute = this.router[this.parentId];

Expand Down Expand Up @@ -1326,10 +1296,22 @@ define('src/route',[
* @returns {boolean}
*/
match: function (url, req) {
var params = Url.match(this.url.regexp, url.pathname),
query = url.query,
_paramsRules = this.url.params,
_queryRules = this.url.query;
return this._matchWithAnyUrl(this.url, url, req);
},

/**
* Проверка маршрута с любым URL
* @param {URL} matcherUrl - url, который проверяем
* @param {URL} matchingUrl - url, с которым проверяем
* @param {Pilot.Request} req
* @returns {boolean}
* @private
*/
_matchWithAnyUrl: function (matcherUrl, matchingUrl, req) {
var params = Url.match(matcherUrl.regexp, matchingUrl.pathname),
query = matchingUrl.query,
_paramsRules = matcherUrl.params,
_queryRules = matcherUrl.query;

return (
params &&
Expand Down Expand Up @@ -1745,6 +1727,19 @@ define('src/pilot.js',[

// Находим нужный нам маршрут
currentRoute = routes.find(function (/** Pilot.Route */item) {
// Пытаемся сматчить этот маршрут по алиасу
var matchedAlias = item.aliases.find(function (alias) {
var matcher = alias.matcher;
return matcher && matcher(url, req);
});

// Получилось?
if (matchedAlias) {
req.alias = matchedAlias.name;
return true;
}

// Матчим по основной регулярке
return !item.__group__ && item.match(url, req);
});

Expand Down Expand Up @@ -1963,4 +1958,4 @@ define('src/pilot.js',[
return Pilot;
});

});
});
Loading