Handlebars.js: sugerencia de función | Ayudantes sincrónicos/asincrónicos

Creado en 23 ene. 2014  ·  24Comentarios  ·  Fuente: handlebars-lang/handlebars.js

Registrar un ayudante como Sync o Async ayuda a escuchar las devoluciones de llamada y obtener los datos de la devolución de llamada.

Comentario más útil

voy a probar express-hbs, pero creo que en 2018 es algo extraño no admitirlo. Sé que hay una visión purista de que las cosas asíncronas no deben hacerse como parte de la vista, sino en algo mágico llamado controlador (como si MVC fuera de alguna manera indiscutiblemente "correcto"), pero eso es un poco miope para dos teclas razones

a) bibliotecas externas: la mayoría de las personas ahora escriben completamente con async/await; creo que en mi código, más de 9 de cada 10 funciones son asíncronas ... en algunos casos, "por si acaso". No admitir una función asíncrona significa que todas las bibliotecas asíncronas son repentinamente completamente inaccesibles

b) funciones de controlador genéricas. Yo diría que algo como esto:

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

es más corto, más limpio, más fácil de mantener y básicamente superior en todas las formas imaginables en comparación con tener una función de controlador a medida que inserta esas cosas en una variable y las pasa a una plantilla, a la que la plantilla debe conocer y acceder.

Todos 24 comentarios

Esto surgió en el pasado, pero no actuamos en consecuencia porque el caso de uso no estaba claro. Dado que handlebars todavía tiene que esperar hasta que todos los datos estén disponibles para procesar, proporcionar una evaluación asíncrona es simplemente una conveniencia para algo que el código que genera el contexto puede hacer de una manera mucho más eficiente.

Básicamente, agregar una evaluación asíncrona en este punto sería bastante costoso tanto en términos de compatibilidad como de rendimiento en tiempo de ejecución, por lo que no veo un caso de uso en este momento. ¿Tienes un ejemplo concreto de lo que estás tratando de hacer?

Estoy de acuerdo con los problemas de rendimiento, pero creo que será mejor, si opcionalmente podemos hacerlo aysnc, por ejemplo, RegisterHelper & RegisterHelperAsync, o algo así.

En realidad, llegué a pensar en estos manillares asíncronos, mientras trabajo con node.js. Estoy trabajando en algunas aplicaciones usando express.js y el motor de plantilla que uso es el manillar. Entonces, si necesito obtener algún valor de una llamada a la base de datos, mientras compilo la vista, no es posible con este trabajo síncrono.

Por ejemplo,

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

El ejemplo anterior no funcionará y no devuelve nada. El siguiente es mi concepto. Y, no sé, si es totalmente correcto o se puede implementar o no. Simplemente usando una función de devolución de llamada en lugar de devolución, en el caso del método asíncrono.

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.
     });
});

Estoy experimentando más problemas como el anterior y puedo mostrar más ejemplos de acuerdo con mi escenario, si alguien está interesado en esta característica.

Gracias

@robincsamuel , cuando se incluyen búsquedas en la base de datos en la generación de vistas, toda la idea de la separación de MVC pasa a la ventana. Creo que está argumentando que es posible que no sepa que necesita los datos hasta que se represente la vista, pero para mí eso sugiere una funcionalidad que debe implementarse en el nivel del controlador en lugar de generar su vista. Combinado con las consideraciones de rendimiento que mencionó @kpdecker , los ayudantes asíncronos simplemente parecen incorrectos. -- mi 2c

Acabo de usar ese ejemplo para transmitir mi problema. Y no estoy tratando de discutir, sino que solo hice una sugerencia. Espero que ayude si llamamos a una función con devolución de llamada del ayudante. De todos modos, gracias por su tiempo :) @kpdecker @jwilm

En este punto, la postura del proyecto es que la resolución de datos debe realizarse antes de llamar a la plantilla. Fuera de la lógica comercial dentro o fuera de las preocupaciones de la plantilla, la resolución asíncrona es más un comportamiento de utilidad que otras bibliotecas como asíncrona son mucho más adecuadas para manejar.

quiero comentar algo Aunque sería tirar por la borda el ejemplo de base de datos, esto podría ser realmente útil para las plantillas mutacionales. Por ejemplo, una plantilla con "subvistas" dentro y que no desea dividir en varias otras plantillas. Solo quiero actualizar una parte en la vista y tener una lógica simple para eso, en lugar de volver a pintar toda mi vista (efecto de parpadeo) o hacer que mi controlador construya todo para todas estas "mini vistas".

¿Qué piensas?

@tomasdev Me refiero a eso :)

Esta característica está disponible en el nodo con express si usa https://github.com/barc/express-hbs. Sin embargo, la versión asíncrona de los ayudantes no funciona bien con las subexpresiones y algunos otros casos extremos.

Me gustaría ver que se reconsidere esta característica para incluirla en los manillares, o al menos considerar cómo el núcleo del manillar puede admitir mejor este tipo de extensión.

Creo que Ghost demuestra un caso de uso claro y válido (aunque quizás poco común) para los ayudantes asíncronos, porque nuestra capa de vista es personalizable.

En la interfaz, todas las plantillas de Ghost son proporcionadas por el tema. El tema es una capa muy delgada de handlebars, CSS y cliente JS, los únicos datos a los que tiene acceso son los que proporcionamos por adelantado. No tiene acceso a un controlador ni a ninguna lógica de cambio de comportamiento. Esto es muy deliberado.

Para expandir la API del tema, queremos comenzar a agregar ayudantes que definan recopilaciones de datos adicionales que el tema quiera usar. Por ejemplo algo como:

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

Ghost tiene una API JSON que está disponible tanto interna como externamente. Entonces, esta consulta de búsqueda se asignaría a nuestra función de etiquetas de búsqueda. No es necesario usar ajax/http para todos los puntos finales; en cambio, un ayudante asíncrono puede obtener estos datos de la API internamente y continuar como de costumbre.

No digo que este sea un caso de uso común, y acepto que rompe el modelo MVC estándar, pero creo que es válido y útil.

@ErisDS ¡Buenas noticias! Y yo tampoco discuto que es un problema común, pero ayuda.

Vale la pena señalar en este caso que muchas de las operaciones para las que actualmente usamos ayudantes asincrónicos son sincrónicas bajo el capó, pero están estructuradas como promesas.

Para dar un ejemplo detallado...

Se accede a todos los datos en Ghost a través de una API interna. Esto incluye información global como la configuración. Las solicitudes a la API de configuración llegan a un caché en memoria previamente rellenado antes de llegar a la base de datos, por lo que en realidad solo estamos devolviendo una variable, pero al estructurar esto como una promesa, es fácil ir a la base de datos si es necesario.

También garantiza que todo sea consistente; de ​​lo contrario, la API de configuración sería síncrona y todas las demás solicitudes de datos internos serían asíncronas, lo que no tendría sentido.

Sé que estructurar todo con promesas puede ser bastante confuso al principio, pero es una de esas cosas sin las que no entiendes cómo vivías una vez que las tienes. Con los generadores que vienen en ES6, el soporte para la resolución asincrónica de funciones se integrará directamente en JavaScript, y este problema similar: https://github.com/wycats/handlebars.js/issues/141 menciona que sería bueno hacer manillares trabajar con rendimiento.

No estoy seguro de cómo la próxima versión de HTMLbars podría impactar en esto, pero creo que al menos merece una mayor discusión.

Me encontré con otro caso de uso al intentar crear un asistente para la resolución de ACL. Esto encajaría muy bien en mis plantillas:

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

Pero el método isAllowed real de node-acl es asíncrono (lo que permite un backend de base de datos, por ejemplo).

Una solución es obtener todos los permisos de usuario de antemano ( allowPermissions ), pero eso es un poco molesto

@kpdecker ¿ Alguna otra idea sobre estos casos de uso?

@ErisDS Entiendo el deseo aquí, pero dudo mucho que esto llegue al idioma en forma de devolución de llamada o promesas. Esto es algo que es muy difícil de hacer limpiamente desde la perspectiva de la API y efectivamente requiere que reescribamos grandes porciones del motor de plantillas para admitirlo. Mi recomendación es que todo esto se maneje antes de que el modelo/fuente de datos ascendente ingrese el ciclo de renderizado.

La idea del rendimiento es interesante, pero si alguien quisiera echar un vistazo a lo que se requeriría allí, sería un proyecto de investigación increíble, pero el soporte del navegador para eso me parece muy lejano y, sinceramente, no me he metido con cualquiera de esas características todavía en cualquiera de mis proyectos.

Solo mis "dos" (bueno, un par) centavos que tal vez quieras considerar:

  • MVC no es sacrosanto. Las cosas no están mal simplemente porque parecen contradecir MVC. Uno tiene que evaluar si las alternativas no brindan beneficios netos positivos sobre el seguimiento estricto de MVC.
  • Si la vista le pide datos al controlador, no modelos directamente, no es una violación del MVC de todos modos, ¿verdad?
  • Tal vez se podría argumentar que hacer que el controlador sepa de antemano todo lo que necesitará la vista es una duplicación de información (es decir, la información "X, Y, Z, W son necesarios" se duplica en vistas y controladores). En otras palabras, nuestra actual la práctica puede ser una violación del principio DRY, que es mucho más importante que MVC, en mi opinión.
  • El impacto en el rendimiento de los ayudantes asíncronos con el fin de cargar solo los modelos necesarios para las vistas que se representan podría compensarse fácilmente cargando menos datos de la base de datos.

Podría ser capaz de ofrecer un mejor ejemplo donde sería útil tener.

Trabajamos mucho con cordova para aplicaciones móviles y necesitamos localizar para muchos idiomas. Cordova ofrece funciones para ayudar con el formato de fechas, números, monedas, etc.
El problema es que todos requieren una devolución de llamada asíncrona.

Ejemplo:

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

Esto sería increíble tener imo.

Encontré el paquete handlebars-async en npm. Pero es un poco más antiguo y no sé si funciona con la versión actual de Handlebars.

También acabo de escribir algo similar para las promesas. El paquetepromised-handlebars le permite devolver promesas desde dentro de los ayudantes. Planeo usarlo en uno de mis proyectos, pero hasta ahora no se ha usado en un entorno de producción. Pero hay pruebas unitarias para varios casos extremos (como llamar a los ayudantes asíncronos desde dentro de los ayudantes de bloques asíncronos) y todos son verdes...

@nknapp eso suena increíble! express-hbs tiene soporte asíncrono, y el asíncrono funciona para los ayudantes de bloque, pero anidar los ayudantes asíncronos no funciona, por lo que estoy realmente interesado en ver que esto funcione, lo que significa que todavía hay esperanza para express-hbs :+1:

@ErisDS , ¿crees que debería publicarlo allí? No sabía que express-hbs no podía anidar el ayudante asíncrono. Mi enfoque principal no es express , sino un generador README en el que estoy trabajando actualmente. Realmente agradecería que otras personas lo prueben ( manillar prometido) y den su opinión.

Para agregar a los casos de uso válidos, ¿qué sucede si necesita obtener valores de una base de datos de traducción según la configuración regional actual?

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

Además, ¿qué hay de agregar un bloque CMS desde una entrada de base de datos usando una identificación dinámica como:

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

Esto es especialmente útil para el renderizado del lado del servidor (es decir, usando express-handlebars ).

Aquí hay otro caso de uso: estoy escribiendo un generador de documentación para Swagger ( simple-swagger ), que permite definiciones de esquemas externos. Me gustaría escribir un asistente de Handlebars que reconozca cuando un esquema se define externamente, vaya a la URL proporcionada donde vive ese esquema, lo recupere y use esos datos para representar esa parte de la plantilla de Handlebars. Si tuviera que recuperar estos datos antes de llamar al método de compilación de Handlebars, tendría que iterar recursivamente a través de un documento JSON cuya estructura no conozco de antemano, encontrar todas las instancias de esquemas externos, recuperarlos e insertarlos en el JSON.

Básicamente, cada vez que se usa una plantilla de Handlebars para representar datos de esquema JSON ( json-schema.org ), sería útil un método de representación asíncrono, porque el esquema JSON siempre permite que las subpartes de un esquema se definan externamente.

@dwhieb , ¿ha echado un vistazo a bootprint-swagger para el generador de documentación? Es casi lo que describe (excepto que los esquemas externos aún no están implementados, pero eso sería una gran característica). Si tiene algún comentario, abra un problema allí.

Y creo que los manillares prometidos funcionan bastante bien con ayudantes asíncronos.

Tengo un caso de uso en el que sería útil poder usar promesas en ayudantes. Estoy usando handlebars para generar el HTML de mi blog. Para generar datos estructurados válidos para cada artículo, necesito obtener las dimensiones que estoy usando para la imagen del artículo. En este momento, lo estoy haciendo así:

{{#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}}

El ayudante imageSize funciona porque lee el archivo de forma síncrona, pero idealmente debería poder hacerlo de forma asíncrona para que la E/S no ralentice la representación de las páginas. Además, hacer esto para una imagen en una URL, en lugar de hacerlo en el sistema de archivos, es imposible.

Estudiaré el uso de las palancas prometidas y las hbs expresas, pero creo que la capacidad de usar promesas en las funciones auxiliares sería una gran adición a las barras manillares.

FWIW, he estado haciendo una gran cantidad de renderizado HTML asincrónico usando Hyperscript, hyperscript-helpers , async/await de ES7, y ha sido una verdadera alegría. Pero, por supuesto, esa solución solo funciona para HTML. Una solución asíncrona con Handlebars nos permitiría generar otros tipos de archivos de forma asíncrona... Sin embargo, para HTML, ¡creo que nunca miraré hacia atrás!

voy a probar express-hbs, pero creo que en 2018 es algo extraño no admitirlo. Sé que hay una visión purista de que las cosas asíncronas no deben hacerse como parte de la vista, sino en algo mágico llamado controlador (como si MVC fuera de alguna manera indiscutiblemente "correcto"), pero eso es un poco miope para dos teclas razones

a) bibliotecas externas: la mayoría de las personas ahora escriben completamente con async/await; creo que en mi código, más de 9 de cada 10 funciones son asíncronas ... en algunos casos, "por si acaso". No admitir una función asíncrona significa que todas las bibliotecas asíncronas son repentinamente completamente inaccesibles

b) funciones de controlador genéricas. Yo diría que algo como esto:

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

es más corto, más limpio, más fácil de mantener y básicamente superior en todas las formas imaginables en comparación con tener una función de controlador a medida que inserta esas cosas en una variable y las pasa a una plantilla, a la que la plantilla debe conocer y acceder.

¿Fue útil esta página
0 / 5 - 0 calificaciones