Qual é o antipadrão explícito de construção de promessa e como posso evitá-lo?

May 22 2014

Eu estava escrevendo um código que faz algo parecido com:

function getStuffDone(param) {           | function getStuffDone(param) {
    var d = Q.defer(); /* or $q.defer */ |     return new Promise(function(resolve, reject) {
    // or = new $.Deferred() etc.        |     // using a promise constructor
    myPromiseFn(param+1)                 |         myPromiseFn(param+1)
    .then(function(val) { /* or .done */ |         .then(function(val) {
        d.resolve(val);                  |             resolve(val);
    }).catch(function(err) { /* .fail */ |         }).catch(function(err) {
        d.reject(err);                   |             reject(err);
    });                                  |         });
    return d.promise; /* or promise() */ |     });
}                                        | }

Alguém me disse que isso é chamado de " antipadrão adiado " ou " Promiseantipadrão construtor " respectivamente. O que há de ruim nesse código e por que ele é chamado de antipadrão ?

Respostas

380 BenjaminGruenbaum May 22 2014 at 17:07

O antipadrão adiado (agora anti-padrão de construção explícita) cunhado por Esailija é um antipadrão comum que as pessoas que são novas em promessas fazem, eu mesmo fiz quando usei promessas pela primeira vez. O problema com o código acima é que ele não consegue utilizar o fato de que promete cadeia.

As promessas podem ser encadeadas .thene você pode devolver as promessas diretamente. Seu código getStuffDonepode ser reescrito como:

function getStuffDone(param){
    return myPromiseFn(param+1); // much nicer, right?
}

As promessas referem-se a tornar o código assíncrono mais legível e se comportar como o código síncrono, sem ocultar esse fato. As promessas representam uma abstração sobre o valor de uma operação única, elas abstraem a noção de uma instrução ou expressão em uma linguagem de programação.

Você só deve usar objetos adiados quando estiver convertendo uma API em promessas e não puder fazer isso automaticamente, ou quando estiver escrevendo funções de agregação que são mais fáceis de expressar dessa maneira.

Citando Esailija:

Este é o antipadrão mais comum. É fácil cair nisso quando você realmente não entende as promessas e pensa nelas como emissores de eventos glorificados ou utilitário de retorno de chamada. Vamos recapitular: as promessas são sobre como fazer com que o código assíncrono retenha a maioria das propriedades perdidas do código síncrono, como indentação plana e um canal de exceção.

142 Bergi Aug 29 2014 at 20:28

O que há de errado com isso?

Mas o padrão funciona!

Sortudo. Infelizmente, provavelmente não, pois você provavelmente esqueceu algum caso extremo. Em mais da metade das ocorrências que vi, o autor se esqueceu de cuidar do manipulador de erros:

return new Promise(function(resolve) {
    getOtherPromise().then(function(result) {
        resolve(result.property.example);
    });
})

Se a outra promessa for rejeitada, isso acontecerá despercebido em vez de ser propagado para a nova promessa (onde seria tratado) - e a nova promessa fica para sempre pendente, o que pode induzir vazamentos.

A mesma coisa acontece no caso de seu código de retorno de chamada causar um erro - por exemplo, quando resultnão tem um propertye uma exceção é lançada. Isso ficaria sem tratamento e deixaria a nova promessa sem solução.

Em contraste, o uso .then()cuida automaticamente de ambos os cenários e rejeita a nova promessa quando ocorre um erro:

 return getOtherPromise().then(function(result) {
     return result.property.example;
 })

O antipadrão diferido não é apenas complicado, mas também sujeito a erros . Usar .then()para encadeamento é muito mais seguro.

Mas eu cuidei de tudo!

Realmente? Boa. No entanto, isso será muito detalhado e abundante, especialmente se você usar uma biblioteca de promessas que ofereça suporte a outros recursos, como cancelamento ou troca de mensagens. Ou talvez isso aconteça no futuro, ou você deseja trocar sua biblioteca por uma melhor? Você não vai querer reescrever seu código para isso.

Os métodos das bibliotecas ( then) não suportam apenas nativamente todos os recursos, eles também podem ter certas otimizações no local. Usá-los provavelmente tornará seu código mais rápido ou, pelo menos, permitirá que ele seja otimizado por futuras revisões da biblioteca.

Como posso evitar isso?

Portanto, sempre que você criar manualmente um Promiseou Deferrede as promessas já existentes estiverem envolvidas, verifique primeiro a API da biblioteca . O antipadrão adiado é frequentemente aplicado por pessoas que vêem as promessas [apenas] como um padrão de observador - mas as promessas são mais do que retornos de chamada : elas devem ser combinadas. Toda biblioteca decente tem muitas funções fáceis de usar para a composição de promessas de todas as maneiras imagináveis, cuidando de todas as coisas de baixo nível com as quais você não quer lidar.

Se você encontrou a necessidade de redigir algumas promessas de uma nova maneira que não é suportada por uma função auxiliar existente, escrever sua própria função com adiamentos inevitáveis ​​deve ser sua última opção. Considere mudar para uma biblioteca com mais recursos e / ou arquive um bug em sua biblioteca atual. Seu mantenedor deve ser capaz de derivar a composição de funções existentes, implementar uma nova função auxiliar para você e / ou ajudar a identificar os casos extremos que precisam ser tratados.