Padrão de programação funcional para código JavaScript assíncrono

5

Adotei um padrão em um dos meus projetos que realmente gosto e suspeito que seja algo padrão. Eu gostaria de descrevê-lo aqui e ver se vocês podem me dizer como outros programadores JavaScript resolvem esse tipo de problema.

Algumas regras que estabeleci para mim mesmo antes de começar o projeto. 1. Avaliação funcional / preguiçosa, sempre que possível. Não faça um monte de coisas na inicialização. 2. Use promessas (por acaso eu estou usando o $ q do Angular, mas a sintaxe é muito parecida)

Por uma questão de simplicidade, digamos que estou querendo atualizar a tela e exibir no topo do nome do usuário, quantos Gostos eles têm e sua contagem atual de amigos. Algumas dessas coisas podem parecer artificiais, mas nuas comigo.

No meu controlador no lado do cliente, eu faria chamadas simples para um serviço do lado do cliente que fizesse algo como:

   function getCurrentUserName() {
       getCurrentUser().then( function(user) {
           return user.name;
       }
   }

   function getCurrentUserLikeCount() {
       getCurrentUser().then( function(user) {
           return user.likeCount;
       }
   }

   function getCurrentUserFriendCount() {
       getCurrentUser().then( function(user) {
           return user.friendCount;
       }
   }

Agora, posso ter vários elementos de interface do usuário que acessam o controlador e quem sabe em que ordem os pedidos podem chegar. Claro, é possível que haja um widget de terceiros que acessem o controlador, então pode haver várias chamadas para uma dessas rotinas chegando rapidamente.

Agora, o primeiro problema óbvio é que getCurrentUser () é assíncrono e faz algum tipo de chamada ajax / rest para o servidor. Assim: 1) Você não precisa fazer chamadas desnecessárias para o servidor. Se getCurrentUser () já tiver sido chamado, você não precisará chamá-lo novamente (dado algum raciocínio como o usuário ainda está logado, etc.)

Então, vamos supor que getCurrentUser () seja inteligente o suficiente para armazenar os resultados em cache. Em chamadas subseqüentes, ele pode retornar o valor em cache (ainda de forma assíncrona usando when ()).

O segundo problema é o que acontece quando getCurrentUser () é chamado enquanto outra chamada para getCurrentUser () já está sendo processada. O cache ainda não foi atualizado, então a segunda chamada não vê o cache, então faz outra chamada HTTP para o servidor. Você não quer isso. Então você precisa saber que há uma solicitação pendente para o servidor e acelera a chamada.

Portanto, o padrão que adotei é que uma função assíncrona que faz uma chamada para o servidor segue a seguinte idéia de alto nível: 1. Se for a primeira chamada, emita a chamada ao servidor e retorne imediatamente uma promessa ao cliente. Além disso, mantenha essa promessa. Quando o servidor responder, resolva a promessa, mas também mantenha o valor original retornado do servidor (por exemplo, cache)

  1. Se não for a primeira chamada, determine se a primeira chamada foi concluída. Se o primeiro chamado já tiver sido concluído, retorne o valor que foi retornado do servidor (ou seja, o cache). Claro, você faria isso usando when () desde que o cliente está esperando uma promessa.

  2. Se não for a primeira chamada, mas a primeira chamada ainda não foi concluída, retorne ao cliente a promessa em cache que foi retornada pela primeira chamada. Quando a promessa for resolvida, todos os clientes serão notificados.

Na verdade, o que também funciona com o $ q angular (e provavelmente outros), é reutilizar a promessa original mesmo depois de ter sido devolvida no passado. Então você pode evitar o armazenamento em cache dos dados, já que a promessa contém os dados retornados do servidor.

Eu chamo isso de afogamento, por falta de um termo melhor.

A função a seguir envolve uma função assíncrona existente e cria uma nova função que limita automaticamente:

    // This routine makes a custom throttle wrapper for the async-function that's passed in.
    // A throttle function, when called, calls an async function (one that returns a promise)
    // in such a way to ensure the async function is only called when it's not already in progress.
    // in which case rather than calling it again, this routine returns the original promise to the caller.
    // If it's already in progress, then the original promise is returned instead.
    var makeThrottleFunction = function (asyncFunction) {
        var asyncPromise = null;
        return function (asyncInParam) {
            if (!asyncPromise) {
                var asyncDeferred = $q.defer();
                asyncPromise = asyncDeferred.promise;
                asyncFunction(asyncInParam).then(function (asyncOutParam) {
                    asyncDeferred.resolve(asyncOutParam);
                }).catch(function (parameters) {
                    return $q.reject(parameters);
                }).finally(function () {
                    asyncPromise = null;
                });
            }
            return asyncPromise;
        };
    };

Exemplo de uso:

var getCurrentUser = makeThrottleFunction(function() {
    // async call to server to get data
    // return promise..
});

Estou curioso sobre o que os especialistas têm a dizer sobre essa abordagem. O que é uma alternativa, etc. Eu usei em todo o lugar no meu aplicativo e funciona muito bem. Eu também não acho que poderia ficar sem isso, o que levanta a questão: se algo assim já não está nos frameworks, então eu posso estar perdendo algo óbvio, como alguma configuração ou biblioteca que já faz isso exatamente. Então, eu adoraria receber algum feedback.

Embora eu esteja procurando opiniões de outras pessoas sobre como eles resolvem esse problema comum, para que isso seja um Q & A, acho que uma boa resposta seria uma das seguintes: 1. Aponte para uma biblioteca que faz esse tipo de coisa. 2. Salienta que há uma maneira mais simples de fazer isso.

    
por zumalifeguard 12.11.2014 / 18:13
fonte

1 resposta

5

Não estou ciente de uma implementação publicada dessa técnica, mas é uma maneira bastante padrão de usar promessas. Um efeito colateral interessante da composibilidade, reutilização e extensibilidade da programação funcional é que muitas vezes você não encontra coisas nas bibliotecas que você esperaria.

Em um paradigma menos composable, você precisaria de um suporte em uma biblioteca para fazer algo parecido com sua função throttle, porque você tem que tecer suporte para ele através de outras funções. No código funcional, os programadores escrevem a si mesmos apenas uma vez, então eles podem reutilizá-lo em todos os lugares.

Depois de escrever algo assim, com quem você compartilha e como? É muito pequeno para ser sua própria biblioteca. É um pouco específico demais para não ser considerado incômodo em uma biblioteca promissora. Talvez algum tipo de biblioteca de utilidades promissoras, mas seria em um módulo não muito coeso, com outras funções vagamente relacionadas a promessas, o que dificulta a sua localização.

O que acontece comigo é que eu procuro 10 minutos ou mais, depois escrevo sozinho porque só me levaria meia hora. Eu o coloco em um arquivo de "utilitários" que eu mantenho com outras funções que são curtas mas altamente reutilizáveis, mas que não se encaixam em lugar algum. Então, seis meses depois, aconteço por acaso em uma biblioteca mantida pela comunidade com um nome estranho.

Meu ponto é, com programação funcional, não ser capaz de encontrar uma implementação existente para algo que se sente razoavelmente "padrão" não é um sinal de que outras pessoas estão fazendo isso de uma maneira superior, ou que você é o primeiro para chegar a ele. É apenas um sintoma da dificuldade de compartilhar códigos curtos e reutilizáveis de uma forma fácil de ser descoberta por uma pesquisa.

    
por 12.11.2014 / 19:31
fonte