Qual é o antipadrão da construção de promessa explícita e como evito isso?

516

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 diferido " ou " Promiseconstrutor antipadrão ", respectivamente, o que há de ruim nesse código e por que isso é chamado de antipadrão ?

Benjamin Gruenbaum
fonte
Posso confirmar que a remoção disso é (no contexto da direita, não da esquerda, exemplo) remover o getStuffDonewrapper da função e apenas usar o literal Promise?
The Dembinski
1
ou está tendo o catchbloco no getStuffDoneinvólucro o antipadrão?
The Dembinski
1
Pelo menos para o Promiseexemplo nativo, você também possui invólucros de funções desnecessários para os manipuladores .thene .catch(isto é, poderia ser .then(resolve).catch(reject)). Uma tempestade perfeita de antipadrões.
Noah Freitas
6
@NoahFreitas esse código é escrito dessa maneira para fins didáticos. Eu escrevi essa pergunta e resposta, a fim de ajudar as pessoas que se deparam com esta questão depois de ler um monte de código parecendo que :)
Benjamin Gruenbaum
Consulte também stackoverflow.com/questions/57661537/… para saber como eliminar não apenas a construção explícita do Promise, mas também o uso de uma variável global.
David Spector

Respostas:

357

O antipadrão diferido (agora antipadrão de construção explícita) cunhado por Esailija é um povo antipadrão comum, que é novo nas promessas, eu mesmo fiz quando usei promessas pela primeira vez. O problema com o código acima é que falha ao utilizar o fato que promete a cadeia.

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

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

As promessas são sobre tornar o código assíncrono mais legível e se comportar como código síncrono sem ocultar esse fato. Promessas representam uma abstração sobre um valor de uma operação única, abstraem a noção de uma declaraçã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 fazê-lo automaticamente, ou quando estiver escrevendo funções de agregação que sejam mais facilmente expressas dessa maneira.

Citando Esailija:

Este é o anti-padrão mais comum. É fácil entender isso 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 fazer com que o código assíncrono retenha a maioria das propriedades perdidas do código síncrono, como recuo plano e um canal de exceção.

Benjamin Gruenbaum
fonte
@BenjaminGruenbaum: Estou confiante no uso de diferidos para isso, então não há necessidade de uma nova pergunta. Eu apenas pensei que era um caso de uso que estava faltando na sua resposta. O que estou fazendo parece mais o oposto da agregação, não é?
Mhelvens
1
@mhelvens Se você estiver dividindo manualmente uma API sem retorno de chamada em uma API de promessa que se encaixa na parte "converter uma API de retorno de chamada em promessas". O antipadrão é sobre cumprir uma promessa em outra promessa sem uma boa razão; você não está cumprindo uma promessa para começar, para que não se aplique aqui.
Benjamin Gruenbaum
@BenjaminGruenbaum: Ah, embora os diferidos fossem considerados um antipadrão, o que com o bluebird os deprecia e você mencionou "converter uma API em promessas" (que também é um caso de não cumprir uma promessa).
Mhelvens
@mhelvens Eu acho que o excesso de anti-adiado padrão seria mais preciso para o que realmente faz. Bluebird obsoleta a .defer()api para o mais recente (e jogar seguro) construtor promessa, isso não aconteceu (de maneira nenhuma) depreciar a noção de promessas construindo :)
Benjamin Gruenbaum
1
Obrigado @ Roamer-1888, sua referência me ajudou a finalmente descobrir qual era o meu problema. Parece que eu estava criando promessas aninhadas (não devolvidas) sem perceber.
Ghuroo 30/05
134

O que há de errado com isso?

Mas o padrão funciona!

Sortudo. Infelizmente, provavelmente não, pois você provavelmente esqueceu de um caso de ponta. Em mais da metade das ocorrências que vi, o autor 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 tratada) - e a nova promessa permanecerá pendente para sempre, o que pode induzir vazamentos.

O mesmo acontece no caso em que seu código de retorno de chamada causa um erro - por exemplo, quando resultnão há um propertye uma exceção é lançada. Isso não seria tratado e deixaria a nova promessa por resolver.

Por outro lado, o uso .then()cuida automaticamente desses dois 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 pesado, mas também propenso a erros . Usar .then()para encadeamento é muito mais seguro.

Mas eu lidei com tudo!

Mesmo? Boa. No entanto, isso será bastante detalhado e abundante, especialmente se você usar uma biblioteca de promessas que suporte outros recursos, como cancelamento ou passagem de mensagens. Ou talvez seja no futuro, ou você queira 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 apenas suportam nativamente todos os recursos, mas também podem ter certas otimizações em vigor. Seu uso provavelmente tornará seu código mais rápido ou, pelo menos, permitirá que seja otimizado por futuras revisões da biblioteca.

Como evito isso?

Assim, sempre que você encontrar-se criar manualmente um Promiseou Deferrede promessas já existentes estão envolvidos, verificar a API biblioteca primeiro . O antipadrão diferido é frequentemente aplicado por pessoas que vêem [apenas] promessas como um padrão de observador - mas promessas são mais do que retornos de chamada : elas devem ser compostas. Toda biblioteca decente tem muitas funções fáceis de usar para a composição de promessas de todas as maneiras possíveis, cuidando de todas as coisas de baixo nível com as quais você não deseja lidar.

Se você encontrou a necessidade de redigir algumas promessas de uma maneira nova 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 mais abrangente e / ou registrar um bug na sua biblioteca atual. Seu mantenedor deve poder derivar a composição das funções existentes, implementar uma nova função auxiliar para você e / ou ajudar a identificar os casos extremos que precisam ser tratados.

Bergi
fonte
Existem exemplos, além de uma função setTimeout, incluindo onde o construtor poderia ser usado, mas não ser considerado "Promit anitpattern constructor"?
guest271314
1
@ guest271314: Tudo assíncrono que não retorna uma promessa. Embora com freqüência suficiente, você obtém melhores resultados com os auxiliares dedicados de promisificação das bibliotecas. E certifique-se de sempre promisificar no nível mais baixo, para que não seja " uma função incluindosetTimeout ", mas " a setTimeoutprópria função ".
#
"E certifique-se de sempre promisificar no nível mais baixo, para que não seja" uma função que inclua setTimeout", mas" a setTimeoutprópria função "". Pode descrever, vincular-se a diferenças entre os dois?
guest271314
@ guest271314 Uma função que inclui apenas uma chamada setTimeouté claramente diferente da própria funçãosetTimeout , não é?
#
4
Eu acho que uma das lições importantes aqui, que ainda não foi claramente mencionada até agora, é que uma Promise e seu encadernado 'then' representam uma operação assíncrona: a operação inicial está no construtor Promise e o ponto final final está no ' então 'função. Portanto, se você tiver uma operação de sincronização seguida de uma operação assíncrona, coloque o material de sincronização na Promessa. Se você tiver uma operação assíncrona seguida por uma sincronização, coloque o material de sincronização no 'then'. No primeiro caso, retorne a promessa original. No segundo caso, retorne a cadeia Promise / then (que também é uma Promise).
David Spector