Handlebars.js: предложение функции | Синхронные/асинхронные помощники

Созданный на 23 янв. 2014  ·  24Комментарии  ·  Источник: handlebars-lang/handlebars.js

Регистрация помощника как Sync или Async помогает прослушивать обратные вызовы и получать данные из обратного вызова.

Самый полезный комментарий

собираюсь попробовать express-hbs, но я думаю, что в 2018 году странно не поддерживать. Я знаю, что существует пуристское мнение, что асинхронные вещи должны выполняться не как часть представления, а вместо этого в какой-то волшебной вещи, называемой контроллером (как если бы MVC каким-то образом бесспорно «правилен»), но это немного недальновидно для двух ключевых причины

а) внешние библиотеки - большинство людей сейчас пишут полностью с помощью async/await - я думаю, что в моем коде более 9 из 10 функций являются асинхронными... в некоторых случаях "на всякий случай". Отсутствие поддержки асинхронной функции означает, что все асинхронные библиотеки внезапно становятся полностью недоступными.

б) общие функции контроллера. Я бы сказал, что что-то вроде этого:

    {{#query "select name, total from summary"}}
          <tr><td>{{this.name}}</td><td>{{this.total}}</td></tr>
    {{/query}}

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

Все 24 Комментарий

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

По сути, добавление асинхронной оценки на этом этапе было бы довольно дорого как с точки зрения совместимости, так и с точки зрения производительности во время выполнения, что я не вижу возможности использования прямо сейчас. У вас есть конкретный пример того, что вы пытаетесь сделать?

Я согласен с проблемами производительности, но я думаю, что будет лучше, если мы сможем опционально сделать это aysnc, например, RegisterHelper & RegisterHelperAsync или что-то в этом роде.

На самом деле я задумался об этих асинхронных рулях, пока работаю с node.js. Я работаю над некоторыми приложениями, используя express.js, а механизм шаблонов, который я использую, - это руль. Итак, если мне нужно получить какое-то значение из вызова базы данных при компиляции представления, это невозможно с этой синхронной работой,

Например,

Handlebars.registerHelper('getDbValue', function(id) {
     var Model = require('./myModel.js');
     Model.getValue(id, function(data){
           return data;
     });
});

Приведенный выше пример не будет работать и ничего не возвращает. Далее моя концепция. И я не знаю, правильно ли это, реализуемо или нет. Просто используйте функцию обратного вызова вместо возврата в случае асинхронного метода.

Handlebars.registerHelperAsync('getDbValue', function(id, callback) {
     var Model = require('./myModel.js');
     Model.getValue(id, function(data){
           callback(data);
           //or
           //callback(new Handlebars.SafeString(data)); //in case of safestring.
     });
});

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

Спасибо

@robincsamuel , при включении поиска в базе данных в генерацию представлений вся идея разделения MVC уходит в окно. Я думаю, вы утверждаете, что вы можете не знать, что вам нужны данные, пока представление не будет отображено, но для меня это предлагает функциональность, которая должна быть реализована на уровне контроллера, а не при создании вашего представления. В сочетании с соображениями производительности, упомянутыми @kpdecker , асинхронные помощники просто кажутся неправильными. -- мой 2с

Я просто использовал этот пример, чтобы передать свою проблему. И я не пытаюсь спорить, а просто предложил. Надеюсь, поможет, если мы вызовем функцию с обратным вызовом из хелпера. В любом случае, спасибо за ваше время :) @kpdecker @jwilm

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

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

Что вы думаете?

@tomasdev Я имею в виду :)

Эта функция доступна в узле с экспрессом, если вы используете https://github.com/barc/express-hbs. Однако асинхронная версия хелперов плохо работает с подвыражениями и некоторыми другими пограничными случаями.

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

Я считаю, что Ghost демонстрирует четкий и действительный (хотя, возможно, необычный) вариант использования асинхронных помощников, потому что наш уровень представления настраивается.

Во внешнем интерфейсе все шаблоны Ghost предоставляются темой. Тема представляет собой очень тонкий слой рулей, CSS и клиентского JS, единственные данные, к которым у нее есть доступ, — это те, которые мы предоставляем заранее. У него нет доступа к контроллеру или какой-либо логике изменения поведения. Это очень преднамеренно.

Чтобы расширить API темы, мы хотим начать добавлять помощники, которые определяют дополнительные наборы данных, которые тема хотела бы использовать. Например что-то вроде:

{{#fetch tags}}
.. do something with the list of tags..
{{else}}
No tags available
{{/fetch}}

Ghost имеет JSON API, который доступен как внутри, так и снаружи. Таким образом, этот запрос на выборку будет отображаться на нашу функцию просмотра тегов. Нет необходимости использовать ajax/http для всех конечных точек, вместо этого асинхронный помощник может получить эти данные из API внутри и продолжить работу в обычном режиме.

Я не утверждаю, что это распространенный вариант использования, и я согласен с тем, что он нарушает стандартную модель MVC, но я считаю, что это правильно и полезно.

@ErisDS Отличные новости! И я тоже не спорю, что это распространенная проблема, но это помогает.

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

Чтобы привести подробный пример...

Все данные в Ghost доступны через внутренний API. Сюда входит глобальная информация, такая как настройки. Запросы к API настроек попадают в предварительно заполненный кеш в памяти, прежде чем попасть в базу данных, поэтому мы на самом деле просто возвращаем переменную, но структурируя это как обещание, мы можем легко обратиться к базе данных, если нам это нужно.

Это также гарантирует, что все согласовано — в противном случае API настроек был бы синхронным, а все остальные внутренние запросы данных были бы асинхронными, что не имело бы смысла.

Я знаю, что структурирование всего с помощью промисов поначалу может сбивать с толку, но это одна из тех вещей, без которых вы не понимаете, как вы жили, когда у вас это есть. С появлением генераторов в ES6 поддержка асинхронного разрешения функций будет встроена прямо в JavaScript — и в этой похожей проблеме: https://github.com/wycats/handlebars.js/issues/141 упоминается, что было бы неплохо сделать рули. работа с выходом.

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

Столкнулся с другим вариантом использования при попытке создать помощник для разрешения списка доступа. Это хорошо вписалось бы в мои шаблоны:

        {{#allowedTo 'edit' '/config'}}
            <li>
                <a href="/config">Config</a>
            </li>
        {{/allowedTo}}

Но фактический метод isAllowed из node-acl является асинхронным (например, позволяет использовать серверную часть базы данных).

Обходной путь — заранее получить все разрешения пользователя ( allowPermissions ), но это немного раздражает.

@kpdecker Есть еще мысли по поводу этих вариантов использования?

@ErisDS Я понимаю желание здесь, но я сильно сомневаюсь, что это когда-либо войдет в язык в форме обратного вызова или обещаний. Это то, что очень сложно сделать чисто с точки зрения API и фактически требует от нас переписать большие части механизма шаблонов для его поддержки. Я рекомендую, чтобы все это было обработано до того, как цикл рендеринга будет введен восходящей моделью/источником данных.

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

Просто мои "два" (ну, пару) центов, которые вы, возможно, захотите рассмотреть:

  • MVC не является священным. Вещи не являются неправильными просто потому, что они кажутся противоречащими MVC. Нужно оценить, не дают ли альтернативы чистых положительных преимуществ по сравнению со строгим следованием MVC.
  • Если представление запрашивает данные у контроллера, а не модели напрямую, это в любом случае не является нарушением MVC, не так ли?
  • Возможно, можно возразить, что наличие у контроллера заранее информации обо всем, что потребуется представлению, является дублированием информации (т. е. информация «Необходимы X, Y, Z, W» дублируется в представлениях и контроллерах). Другими словами, наша текущая практика может быть нарушением принципа DRY, который гораздо важнее, чем MVC, imo.
  • Снижение производительности асинхронных помощников для загрузки только моделей, необходимых для визуализируемых представлений, может быть легко компенсировано загрузкой меньшего количества данных из базы данных.

Я мог бы предложить лучший пример, где было бы полезно иметь.

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

Пример:

Handlebars.registerHelper('stringToNumber', function(string, type)
{
    type = type || 'decimal';
    navigator.globalization.stringToNumber(string, function(number)
    {
        return number;
    }, function()
    {
        return NaN;
    }, {
        type: type
    });
});

Было бы здорово иметь imo.

Я нашел пакет handlebars -async на npm. Но он немного устарел, и я не знаю, работает ли он с текущей версией Handlebars.

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

@nknapp звучит потрясающе! У экспресс-hbs есть поддержка асинхронности, и асинхронность работает для блочных хелперов, но вложенные асинхронные хелперы не работают — поэтому мне очень интересно увидеть, как это работает — значит, есть надежда на экспресс-хбс: +1:

@ErisDS , как вы думаете, я должен опубликовать это там. Я не знал, что express-hbs не может вложить асинхронный помощник. Моя основная цель не express , а генератор README, над которым я сейчас работаю. Я был бы очень признателен, если бы другие люди попробовали это ( обещанные рули) и оставили отзыв.

Чтобы добавить к действительным вариантам использования, что, если вам нужно получить значения из базы данных перевода на основе текущей локали?

<div class="howItWorks">
    {{{i18nFetch id=how-it-works locale=locale}}}
</div>

Более того, как насчет добавления блока CMS из записи БД с использованием динамического идентификатора, например:

<div class="searchCms">
    {{{cmsLoader 'search-{$term}' term=params.input defaultId='search-default'}}}
</div>

Это особенно полезно для рендеринга на стороне сервера (т. е. с использованием экспресс-рулей ).

Вот еще один вариант использования: я пишу генератор документации для Swagger ( simple-swagger ), который позволяет определять внешние схемы. Я хотел бы написать помощник Handlebars, который распознает, когда схема определена извне, переходит к предоставленному URL-адресу, где находится эта схема, извлекает ее и использует эти данные для отображения этой части шаблона Handlebars. Если бы мне пришлось извлекать эти данные перед вызовом метода компиляции Handlebars, мне пришлось бы рекурсивно перебирать документ JSON, структура которого мне заранее неизвестна, находить все экземпляры внешних схем, извлекать их и вставлять в JSON.

По сути, каждый раз, когда шаблон Handlebars используется для рендеринга данных схемы JSON ( json-schema.org ), будет полезен метод асинхронного рендеринга, потому что схема JSON всегда позволяет определять части схемы извне.

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

И я думаю, что promises-handlebars неплохо работает с асинхронными помощниками.

У меня есть вариант использования, когда было бы полезно использовать обещания в помощниках. Я использую handlebars для создания HTML для своего блога. Чтобы построить достоверные структурированные данные для каждой статьи, мне нужно получить размеры, которые я использую для изображения статьи. Прямо сейчас я делаю это так:

{{#imageSize post.frontMatter.previewImage}}
  <div itemprop="image" itemscope itemtype="https://schema.org/ImageObject">
    <meta itemprop="url" content="{{#staticResource ../post.frontMatter.previewImage}}{{/staticResource}}">
    <meta itemprop="width" content="{{width}}">
    <meta itemprop="height" content="{{height}}">
  </div>
{{/imageSize}}

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

Я рассмотрю возможность использования promises-handlebars и express-hbs, но я думаю, что возможность использовать promises во вспомогательных функциях была бы отличным дополнением к Handlebars!

FWIW, я много занимался асинхронным рендерингом HTML с использованием Hyperscript, hyperscript-helpers , async/await ES7, и это было настоящей радостью. Но, конечно, это решение работает только для HTML. Асинхронное решение с Handlebars позволит нам асинхронно генерировать другие типы файлов... Однако для HTML я думаю, что никогда не оглядываюсь назад!

собираюсь попробовать express-hbs, но я думаю, что в 2018 году странно не поддерживать. Я знаю, что существует пуристское мнение, что асинхронные вещи должны выполняться не как часть представления, а вместо этого в какой-то волшебной вещи, называемой контроллером (как если бы MVC каким-то образом бесспорно «правилен»), но это немного недальновидно для двух ключевых причины

а) внешние библиотеки - большинство людей сейчас пишут полностью с помощью async/await - я думаю, что в моем коде более 9 из 10 функций являются асинхронными... в некоторых случаях "на всякий случай". Отсутствие поддержки асинхронной функции означает, что все асинхронные библиотеки внезапно становятся полностью недоступными.

б) общие функции контроллера. Я бы сказал, что что-то вроде этого:

    {{#query "select name, total from summary"}}
          <tr><td>{{this.name}}</td><td>{{this.total}}</td></tr>
    {{/query}}

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

Была ли эта страница полезной?
0 / 5 - 0 рейтинги

Смежные вопросы

ShintaroOkuda picture ShintaroOkuda  ·  7Комментарии

rizen picture rizen  ·  6Комментарии

amirzandi picture amirzandi  ·  7Комментарии

morgondag picture morgondag  ·  5Комментарии

stevenvachon picture stevenvachon  ·  7Комментарии