Handlebars.js: sugestão de recurso | Auxiliares Síncronos/Assíncronos

Criado em 23 jan. 2014  ·  24Comentários  ·  Fonte: handlebars-lang/handlebars.js

Registrando um auxiliar como Sync ou Async, ajuda a ouvir retornos de chamada e obter os dados do retorno de chamada.

Comentários muito úteis

vou tentar express-hbs - mas acho que em 2018 é uma coisa estranha não apoiar. Eu sei que existe uma visão purista de que coisas assíncronas não devem ser feitas como parte da visão, mas sim em alguma coisa mágica chamada controlador (como se o MVC fosse de alguma forma indiscutivelmente "correto") - mas isso é um pouco míope para duas chaves razões

a) bibliotecas externas - a maioria das pessoas agora está escrevendo inteiramente com async/await - acho que no meu código mais de 9 em cada 10 funções são assíncronas ... em alguns casos "apenas no caso". Não suportar uma função assíncrona significa que todas as bibliotecas assíncronas são de repente completamente inacessíveis

b) funções genéricas do controlador. Eu diria que algo assim:

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

é mais curto, mais limpo, mais fácil de manter e basicamente superior em todos os aspectos concebíveis quando comparado a ter uma função de controlador sob medida que coloca essas coisas em uma variável e a passa para um modelo, que o modelo precisa conhecer e acessar.

Todos 24 comentários

Isso surgiu no passado, mas não agimos sobre isso porque o caso de uso não estava claro. Como o handlebar ainda precisa esperar até que todos os dados estejam disponíveis para renderização, fornecer avaliação assíncrona é simplesmente uma conveniência para algo que o código que gera o contexto pode fazer de maneira muito mais eficiente.

Basicamente, adicionar avaliação assíncrona neste ponto seria bastante caro em termos de compatibilidade e desempenho de tempo de execução para o qual não vejo um caso de uso no momento. Você tem um exemplo concreto do que está tentando fazer?

Concordo com os problemas de desempenho, mas acho que será melhor, se pudermos opcionalmente torná-lo aysnc, por exemplo, RegisterHelper & RegisterHelperAsync, ou algo assim.

Na verdade, cheguei a pensar nesses guidões assíncronos, enquanto trabalho com node.js. Estou trabalhando em alguns aplicativos usando express.js e o mecanismo de modelo que eu uso é o guidão. Então, se eu precisar obter algum valor de uma chamada de banco de dados, ao compilar a visualização, não é possível com esse trabalho síncrono,

Por exemplo,

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

O exemplo acima não funcionará e não retornará nada. O seguinte é o meu conceito. E, não sei, se está totalmente certo ou se pode ser implementado ou não. Apenas usando uma função de callback em vez de return, no caso de método assí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.
     });
});

Estou enfrentando mais problemas como o acima e posso mostrar mais exemplos de acordo com meu cenário, se alguém estiver interessado nesse recurso.

Obrigado

@robincsamuel , Ao incluir pesquisas de banco de dados na geração de exibição, toda a ideia de separação do MVC vai para a janela. Acho que você está argumentando que pode não saber que precisa dos dados até que a exibição esteja sendo renderizada, mas para mim isso sugere uma funcionalidade que deve ser implementada no nível do controlador, e não ao gerar sua exibição. Combinado com as considerações de desempenho mencionadas pelo @kpdecker , os auxiliares assíncronos parecem errados. -- meu 2c

Eu apenas usei esse exemplo para transmitir meu problema. E eu não estou tentando argumentar, mas, apenas fiz uma sugestão. Espero que ajude se chamarmos uma função com retorno de chamada do auxiliar. De qualquer forma, obrigado pelo seu tempo :) @kpdecker @jwilm

Neste ponto, a postura do projeto é que a resolução de dados deve ser feita antes de chamar o modelo. Fora da lógica de negócios dentro ou fora das preocupações do modelo, a resolução assíncrona é mais um comportamento utilitário que outras bibliotecas, como a assíncrona , são muito mais adequadas ao manuseio.

Eu quero comentar algo. Mesmo que fosse jogar pela janela o exemplo de banco de dados, isso pode ser muito útil para modelos mutacionais. Por exemplo, um modelo com "subvisualizações" dentro e que você não deseja dividir em vários outros modelos. Eu só quero atualizar uma parte na visão e ter uma lógica simples para isso, em vez de repintar toda a minha visão (efeito cintilante) ou ter meu controlador para construir a coisa toda para todas essas "mini visualizações"

O que você acha?

@tomasdev , quero dizer isso :)

Esse recurso é disponibilizado no node com express se você usar https://github.com/barc/express-hbs. No entanto, a versão assíncrona dos auxiliares não funciona bem com subexpressões e alguns outros casos extremos.

Eu gostaria de ver esse recurso reconsiderado para inclusão no guidão, ou pelo menos considerar como o núcleo do guidão pode suportar melhor esse tipo de extensão.

Acredito que o Ghost demonstra um caso de uso claro e válido (embora talvez incomum) para auxiliares assíncronos, porque nossa camada de visualização é personalizável.

No frontend, todos os templates do Ghost são fornecidos pelo tema. O tema é uma camada muito fina de guidão, CSS e cliente JS, os únicos dados aos quais ele tem acesso são os que fornecemos antecipadamente. Ele não tem acesso a um controlador ou qualquer lógica de mudança de comportamento. Isso é muito deliberado.

Para expandir a API do tema, queremos começar a adicionar auxiliares que definem coleções de dados adicionais que o tema gostaria de usar. Por exemplo algo como:

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

O Ghost tem uma API JSON que está disponível interna e externamente. Portanto, essa consulta de busca seria mapeada para nossa função de tags de navegação. Ele não precisa usar ajax/http para todo o endpoint, em vez disso, um auxiliar assíncrono pode buscar esses dados da API internamente e continuar normalmente.

Eu não discuto que este é um caso de uso comum, e aceito que quebra o modelo MVC padrão, mas acredito que seja válido e útil.

@ErisDS Ótima notícia! E eu também não discuto que é um problema comum, mas ajuda.

Vale a pena notar, neste caso, que muitas das operações para as quais estamos usando auxiliares assíncronos são síncronas nos bastidores, mas são estruturadas como promessas.

Para dar um exemplo detalhado...

Todos os dados no Ghost são acessados ​​por meio de uma API interna. Isso inclui informações globais, como configurações. As solicitações para a API de configurações atingem um cache de memória pré-preenchido antes de atingir o banco de dados, então estamos apenas retornando uma variável, mas estruturando isso como uma promessa, é fácil ir para o banco de dados se precisarmos.

Ele também garante que tudo seja consistente - caso contrário, a API de configurações seria síncrona e todas as outras solicitações de dados internos seriam assíncronas, o que não faria sentido.

Eu sei que estruturar tudo com promessas pode ser bastante confuso no começo, mas é uma daquelas coisas que você não entende como viveu sem depois de conseguir. Com os geradores chegando no ES6, o suporte para resolução assíncrona de funções será incorporado ao JavaScript - e este problema semelhante: https://github.com/wycats/handlebars.js/issues/141 menciona que seria bom fazer guidões trabalhar com rendimento.

Não tenho certeza de como o próximo lançamento do HTMLbars pode impactar nisso, mas acho que pelo menos merece mais discussão.

Correu para outro caso de uso ao tentar criar um auxiliar para resolução de acl. Isso se encaixaria muito bem nos meus modelos:

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

Mas o método isAllowed real do node-acl é assíncrono (permitindo um backend de banco de dados, por exemplo).

Uma solução alternativa é buscar todas as permissões do usuário de antemão ( allowedPermissions ), mas isso é meio irritante

@kpdecker Mais alguma ideia sobre esses casos de uso?

@ErisDS Eu entendo o desejo aqui, mas duvido muito que isso chegue ao idioma em forma de retorno de chamada ou promessas. Isso é algo que é muito difícil de fazer de forma limpa do ponto de vista da API e efetivamente exige que reescrevamos grandes partes do mecanismo de modelo para suportá-lo. Minha recomendação é que tudo isso seja tratado antes que o ciclo de renderização seja inserido pelo modelo/fonte de dados upstream.

A ideia de rendimento é interessante, mas se alguém quisesse dar uma olhada no que seria necessário lá, seria um projeto de pesquisa incrível, mas o suporte do navegador para isso parece muito distante para mim e, honestamente, não mexi com qualquer um desses recursos ainda em qualquer um dos meus projetos.

Apenas meus "dois" (bem, alguns) centavos que você pode querer considerar:

  • MVC não é sacrossanto. As coisas não estão erradas simplesmente porque parecem contradizer o MVC. É preciso avaliar se as alternativas não fornecem benefícios líquidos positivos sobre seguir estritamente o MVC.
  • Se a visão pede dados ao controlador, não modelos diretamente, não é uma violação do MVC de qualquer maneira, é?
  • Pode-se argumentar que ter o controlador sabendo de antemão tudo o que a visão precisará é uma duplicação de informações (ou seja, as informações "X, Y, Z, W são necessárias" são duplicadas em exibições e controladores). prática pode ser uma violação do princípio DRY, que é muito mais importante do que MVC, imo.
  • O acerto de desempenho de auxiliares assíncronos com o objetivo de carregar apenas os modelos necessários para as visualizações que estão sendo renderizadas pode ser facilmente compensado carregando menos dados do banco de dados.

Eu poderia ser capaz de oferecer um exemplo melhor onde seria útil ter.

Trabalhamos muito com cordova para aplicativos móveis e precisamos localizar para vários idiomas. Cordova oferece funções para ajudar na formatação de datas, números, moedas e assim por diante.
O problema é que todos eles exigem um retorno de chamada assíncrono.

Exemplo:

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

Isso seria incrível ter imo.

Eu encontrei o pacote handlebars-async no npm. Mas é um pouco mais antigo e não sei se funciona com a versão atual do Handlebars.

Eu também acabei de escrever algo semelhante para promessas. O pacote promise-handlebars permite que você retorne promessas de dentro dos helpers. Pretendo usá-lo em um dos meus projetos, mas até agora não foi usado em um ambiente de produção. Mas existem testes de unidade para vários casos de borda (como chamar ajudantes assíncronos de dentro de ajudantes de bloco assíncronos) e todos eles são verdes ...

@nknapp que soa incrível! express-hbs tem suporte assíncrono, e o assíncrono funciona para ajudantes de bloco, mas aninhar ajudantes assíncronos não funciona - então estou realmente interessado em ver isso funcionando - significa que ainda há esperança para express-hbs :+1:

@ErisDS , você acha que eu deveria postar lá. Eu não sabia que express-hbs não podia aninhar o auxiliar assíncrono. Meu foco principal não é express , mas um gerador README no qual estou trabalhando atualmente. Eu realmente apreciaria outras pessoas para experimentá-lo ( guidão prometido ) dar feedback.

Para adicionar aos casos de uso de uso válidos, e se você precisar buscar valores de um banco de dados de tradução com base na localidade atual?

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

Além disso, que tal adicionar um bloco CMS de uma entrada de banco de dados usando um id dinâmico como:

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

Isso é especialmente útil para renderização do lado do servidor (ou seja, usando express-handlebars ).

Aqui está outro caso de uso: estou escrevendo um gerador de documentação para Swagger ( simple-swagger ), que permite definições de esquemas externos. Eu gostaria de escrever um auxiliar Handlebars que reconhece quando um esquema é definido externamente, acessa a URL fornecida onde esse esquema reside, recupera-o e usa esses dados para renderizar essa parte do modelo Handlebars. Se eu tivesse que recuperar esses dados antes de chamar o método de compilação do Handlebars, eu teria que iterar recursivamente por meio de um documento JSON cuja estrutura eu não conheço de antemão, encontrar todas as instâncias de esquemas externos, recuperá-los e inseri-los no JSON.

Basicamente, sempre que um modelo Handlebars é usado para renderizar dados de esquema JSON ( json-schema.org ), um método de renderização assíncrono seria útil, porque o esquema JSON sempre permite que subpartes de um esquema sejam definidas externamente.

@dwhieb você deu uma olhada no bootprint-swagger para o gerador de documentação? É quase o que você descreve (exceto que os esquemas externos ainda não foram implementados, mas isso seria um ótimo recurso). Se você tiver algum feedback, por favor, abra um problema lá.

E acho que o guidão prometido funciona muito bem com auxiliares assíncronos.

Eu tenho um caso de uso em que poder usar promessas em auxiliares seria útil. Estou usando o guidão para gerar o HTML do meu blog. Para criar dados estruturados válidos para cada artigo, preciso obter as dimensões que estou usando para a imagem do artigo. No momento, estou fazendo assim:

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

O auxiliar imageSize funciona porque lê o arquivo de forma síncrona, mas, idealmente, deve ser capaz de fazê-lo de forma assíncrona para que a renderização de páginas não seja retardada pela E/S. Além disso, fazer isso para uma imagem em um URL, em vez de no sistema de arquivos, é impossível.

Vou olhar para o uso de prometidos-handlebars e express-hbs, mas acho que a capacidade de usar promessas em funções auxiliares seria um ótimo complemento para Handlebars!

FWIW, tenho feito muita renderização assíncrona de HTML usando Hyperscript, hyperscript-helpers , async/await do ES7 e tem sido uma verdadeira alegria. Mas é claro que essa solução só funciona para HTML. Uma solução assíncrona com Handlebars nos permitiria gerar outros tipos de arquivos de forma assíncrona... No entanto, para HTML, acho que nunca vou olhar para trás!

vou tentar express-hbs - mas acho que em 2018 é uma coisa estranha não apoiar. Eu sei que existe uma visão purista de que coisas assíncronas não devem ser feitas como parte da visão, mas sim em alguma coisa mágica chamada controlador (como se o MVC fosse de alguma forma indiscutivelmente "correto") - mas isso é um pouco míope para duas chaves razões

a) bibliotecas externas - a maioria das pessoas agora está escrevendo inteiramente com async/await - acho que no meu código mais de 9 em cada 10 funções são assíncronas ... em alguns casos "apenas no caso". Não suportar uma função assíncrona significa que todas as bibliotecas assíncronas são de repente completamente inacessíveis

b) funções genéricas do controlador. Eu diria que algo assim:

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

é mais curto, mais limpo, mais fácil de manter e basicamente superior em todos os aspectos concebíveis quando comparado a ter uma função de controlador sob medida que coloca essas coisas em uma variável e a passa para um modelo, que o modelo precisa conhecer e acessar.

Esta página foi útil?
0 / 5 - 0 avaliações