Промежуточное ПО KeystoneJS запускается дважды при вызове MongoDB с использованием Model.find()

У меня есть маршрут, который загружает список квартир и отображает их:

app.get( '/condo-list', middleware.loadCondoList, routes.views.condolist );

Промежуточное ПО loadCondoList вызывает модель CondoBuilding и устанавливает результаты в res.locals:

exports.loadCondoList = function loadCondoList( req, res, next ) {

console.log( 'request url: ' + req.url );
console.log( 'getting condo buildings...' );

CondoBuilding.model
    .find()
    .exec( ( err, condos ) => {
        if ( err ) {
            // pass error along
            next( err );
        } else {
            // add CondoBuildings to locals
            res.locals.condoBuildings = condos;
            next();
        }
    });
};

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

request url: /condo-list
getting condo buildings...
GET /condo-list 304 344.532 ms
request url: /condo-list
getting condo buildings...
GET /condo-list 304 317.631 ms

Я воспроизвел это поведение в нескольких браузерах (Chrome, Safari, Firefox) и убедился, что это не происходит ни на каких других маршрутах.

Если я удаляю вызов CondoBuilding.model.find() и просто вызываю next() в теле loadCondoList(), такого поведения не происходит.

Я использую Keystone 4 "keystone": "4.0.0-beta.5", который использует Express 4 "express": "4.14.0"

Ниже приведен полный список маршрутов, которые я запускаю в приложении, если это уместно:

// Setup Route Bindings
exports = module.exports = function ( app ) {

// Views
app.get( '/', routes.views.index );
app.get( '/condo-list', middleware.loadCondoList, routes.views.condolist );
app.get( '/blog/:category?', routes.views.blog );
app.get( '/blog/post/:post', routes.views.post );
app.get( '/about', routes.views.about );
app.get( '/search', middleware.getAccountType, routes.views.search );

app.all( '/contact', routes.views.contact );

};

Представление CondoList:

var keystone = require('keystone');

exports = module.exports = function (req, res) {

var view = new keystone.View(req, res);
var locals = res.locals;

// locals.section is used to set the currently selected
// item in the header navigation.
locals.section = 'condolist';

// Render the view
view.render('condolist');
};

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

ОБНОВЛЕНИЕ

Я последовал совету @phuhgh и запустил приложение в режиме экспресс-отладки. Хотя сразу ничего не бросалось в глаза, я заметил кое-что странное во время запуска приложения.

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

express:router:layer new / +0ms
express:router:route new /blog/post/:post +0ms
express:router:layer new /blog/post/:post +0ms
express:router:route get /blog/post/:post +0ms
express:router:layer new / +0ms
express:router:route new /about +0ms
express:router:layer new /about +0ms
express:router:route get /about +0ms
express:router:layer new / +0ms
express:router:route new /search +1ms
express:router:layer new /search +0ms
express:router:route get /search +0ms

Вот последовательность подготовки маршрута списка квартир:

express:router:layer new / +0ms
express:router:route new /condo-list +0ms
express:router:layer new /condo-list +0ms
express:router:route get /condo-list +0ms
express:router:layer new / +0ms
express:router:route get /condo-list +0ms

Как вы могли заметить, строка express:router:route get /condo-list +0ms повторяется. Я понятия не имею, почему, но я предполагаю, что это как-то связано с проблемой, с которой я сталкиваюсь. Я немного больше копаюсь в этом вопросе, но опять же, любая помощь от кого-то, у кого есть немного больше знаний в этой области, будет очень признательна.

Обновление 2 – трассировка стека

Трассировка стека

Я отладил и прошел через стек шаг за шагом. Я могу проследить путь от функции к функции, и все кажется нормальным, но мой базовый уровень нормальности — это поиск других маршрутов, которые работали правильно. Честно говоря, я бы не знал, что искать, если бы я так глубоко погрузился в внутренности Express.

Наблюдения, которые я сделал, просматривая стек:

  • Трассировка стека одинакова при обоих запусках маршрута /condo-list.
  • Трассировка стека (конечно, за вычетом промежуточного ПО loadCondoList) точно такая же для других маршрутов, которые выполняются правильно (т.е. только один раз).
  • If I add a call to loadCondoList in another route, it also runs properly.
    • e.g. I updated the /about route definition to the following: app.get( '/about', middleware.loadCondoList, routes.views.about ); and it loads the data properly and only runs once.

Есть ли что-то, на что мне следует обратить особое внимание, когда я прохожу код библиотеки Express? Я чувствую себя немного не в своей тарелке и не знаю, что искать.


person n-devr    schedule 31.07.2017    source источник
comment
Можете ли вы опубликовать код для route.views.condolist?   -  person Steve Holgado    schedule 04.08.2017
comment
Добавлен CondoList View — ничего не происходит, так как вся загрузка выполняется промежуточным программным обеспечением.   -  person n-devr    schedule 07.08.2017
comment
попробуйте включить режим экспресс-отладки, как описано здесь expressjs.com/en/guide/debugging.html   -  person phuhgh    schedule 09.08.2017
comment
спасибо @phuhgh - я не знал об этом режиме, попробую!   -  person n-devr    schedule 10.08.2017
comment
Используйте findOne вместо find, это имеет смысл и является хорошим подходом, потому что вы заинтересованы в поиске только одной записи, тогда как использование более поздней записи не повредит.   -  person Bharathvaj Ganesan    schedule 10.08.2017
comment
На самом деле я получаю все CondoBuildings и отображаю их в галерее, поэтому find() подходит для этого варианта использования. Есть ли какое-либо отношение к findOne() по сравнению с find() в отношении проблемы, которую я пытаюсь решить, когда маршрут выполняется дважды?   -  person n-devr    schedule 10.08.2017
comment
@n-devr ты используешь отладчик? Если вы установите точку останова в обратном вызове, просмотр стека, вероятно, скажет вам, что происходит. См. nodejs.org/api/, если вы не знакомы.   -  person phuhgh    schedule 10.08.2017
comment
@phuhgh да, я использую отладчик VS Code - я добавил к вопросу детали того, что я собрал, пройдясь по стеку. Спасибо за продолжение совета.   -  person n-devr    schedule 11.08.2017


Ответы (1)


После нескольких дней отладки я наконец нашел виновника, и он прятался в самом неожиданном месте: в представлении!

Контекст

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

<!-- Condo List Masonry -->
<div class="condo-items">
{{#each condoBuildings}}
    <div class="condo-item {{neighborhood.key}}">
        <div class="condo-thumb">
            <span class="condo-tag tag-art">{{neighborhood.name}}</span>
            <a href="/{{condoUrl}}"><img src="{{{cloudinaryUrl image}}}" alt="{{name}}" /></a>
        </div>
        <div class="condo-body">
            <h3><a class="condo-name" href="#">{{name}}</a></h3>
            <p>{{condoDescription}}</p>
        </div>
    </div>
{{/each}}
</div>

Проблема была вызвана помощником cloudinaryUrl в этой строке:

<a href="/{{condoUrl}}"><img src="{{{cloudinaryUrl image}}}" alt="{{name}}" /></a>

Вот вспомогательный код:

_helpers.cloudinaryUrl = function (context, options) {

    // if we dont pass in a context and just kwargs
    // then `this` refers to our default scope block and kwargs
    // are stored in context.hash
    if (!options && context.hasOwnProperty('hash')) {
        // strategy is to place context kwargs into options
        options = context;
        // bind our default inherited scope into context
        context = this;
    }

    // safe guard to ensure context is never null
    context = context === null ? undefined : context;

    if ((context) && (context.public_id)) {
        options.hash.secure = keystone.get('cloudinary secure') || false;
        var imageName = context.public_id.concat('.', context.format);
        return cloudinary.url(imageName, options.hash);
    }
    else {
        return null;
    }
};

Проблема

Для некоторых моделей CondoBuilding еще не определен image. Это приводит к тому, что аргумент context в методе _helpers.cloudinaryUrl равен undefined. В этих случаях помощник вернет null. Я до сих пор не уверен, почему это приводит к перезагрузке страницы, но я уверен, что это было виновником.

Исправление

Обновление шаблона Handlebars для отображения элемента <img> только в том случае, если изображение существует в модели CondoBuilding. Обновленный код шаблона выглядит следующим образом:

<!-- Condo List Masonry -->
<div class="condo-items">
{{#each condoBuildings}}
    <div class="condo-item {{neighborhood.key}}">
        <div class="condo-thumb">
            <span class="condo-tag tag-art">{{neighborhood.name}}</span>
            <a href="/{{condoUrl}}">{{#if image}}<img src="{{{cloudinaryUrl image}}}" alt="{{name}}" />{{/if}}</a>
        </div>
        <div class="condo-body">
            <h3><a class="condo-name" href="#">{{name}}</a></h3>
            <p>{{condoDescription}}</p>
        </div>
    </div>
{{/each}}

With the addition of the {{#if image}} block to the template the route only runs once, as expected!

Дальнейшие шаги

Улучшением по сравнению с этой реализацией было бы использование изображения-заполнителя для всех CondoBuildings, для которых не определен image. Вскоре я добавлю эту функцию, но я не мог удержаться от обновления с ответом, так как уже несколько дней бьюсь головой об эту проблему.

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

person n-devr    schedule 11.08.2017
comment
рад, что ты нашел это. Express:router:route get/condo-list был проблемой, так как вызывает next(), а следующее снова то же самое (предположительно, за которым следует какой-то обработчик трапецеидальных искажений). Вопрос в том, какого хрена было регистрировать его второй раз? Я бы предположил, что краеугольный камень ... Вам придется спуститься и испачкаться в экспрессе, чтобы знать наверняка. Маршрутизатор Express довольно прост, у меня возникло бы желание поставить в нем точку останова и выяснить это. - person phuhgh; 11.08.2017