Quando. (Sucesso, fracasso) é considerado um antipadrão para promessas?

188

Eu dei uma olhada no FAQ de promessa de pássaro azul , no qual ele menciona que .then(success, fail)é um antipadrão . Não entendo bem sua explicação quanto à tentativa e captura. O que há de errado nisso?

some_promise_call()
.then(function(res) { logger.log(res) }, function(err) { logger.log(err) })

Parece que o exemplo está sugerindo o seguinte como o caminho correto.

some_promise_call()
.then(function(res) { logger.log(res) })
.catch(function(err) { logger.log(err) })

Qual é a diferença?

user2127480
fonte
1
then().catch()é mais legível, pois você não precisa procurar vírgula e investigar se esse retorno de chamada é para êxito ou falha na ramificação.
Krzysztof Safjanowski
7
@KevinB: Não é muita diferença, verifique as respostas
Bergi
12
@KrzysztofSafjanowski - devastado pelo argumento 'parece melhor'. Totalmente errado!
Andrey Popov
6
NOTA: Quando você está usando .catch, não sabe qual etapa causou o problema - dentro da última thenou em outro lugar da cadeia de promessas. Portanto, ele tem sua própria desvantagem.
Vitaly-t
2
Eu sempre adicionar nomes de função ao .then promessa () params para torná-lo legível ou sejasome_promise_call() .then(function fulfilled(res) { logger.log(res) }, function rejected(err) { logger.log(err) })
Shane Rowatt

Respostas:

215

Qual é a diferença?

A .then()chamada retornará uma promessa que será rejeitada caso o retorno de chamada gere um erro. Isso significa que, quando seu sucesso loggerfalhar, o erro será passado para o .catch()retorno de chamada a seguir , mas não para o failretorno de chamada que acompanha o mesmo success.

Aqui está um diagrama de fluxo de controle :

controle fluxograma de então com dois argumentos diagrama de fluxo de controle da cadeia de captura

Para expressá-lo em código síncrono:

// some_promise_call().then(logger.log, logger.log)
then: {
    try {
        var results = some_call();
    } catch(e) {
        logger.log(e);
        break then;
    } // else
        logger.log(results);
}

O segundo log(que é como o primeiro argumento .then()) será executado apenas no caso de nenhuma exceção acontecer. O bloco rotulado ea breakdeclaração sentir um pouco estranho, este é realmente o que python tem try-except-elsepara (leitura recomendada!).

// some_promise_call().then(logger.log).catch(logger.log)
try {
    var results = some_call();
    logger.log(results);
} catch(e) {
    logger.log(e);
}

O catchcriador de logs também tratará exceções da chamada do registrador de sucesso.

Tanto pela diferença.

Eu não entendo bem sua explicação quanto à tentativa e captura

O argumento é que geralmente você deseja capturar erros em todas as etapas do processamento e que não deve usá-lo em cadeias. A expectativa é que você tenha apenas um manipulador final que lide com todos os erros - enquanto, quando você usa o "antipadrão", os erros em alguns dos retornos de chamada não são tratados.

No entanto, esse padrão é realmente muito útil: quando você deseja lidar com erros que ocorreram exatamente nesta etapa e deseja fazer algo completamente diferente quando nenhum erro ocorreu - ou seja, quando o erro é irrecuperável. Esteja ciente de que isso está ramificando seu fluxo de controle. Claro, isso às vezes é desejado.


O que há de errado nisso?

some_promise_call()
.then(function(res) { logger.log(res) }, function(err) { logger.log(err) })

Que você teve que repetir seu retorno de chamada. Você prefere

some_promise_call()
   .catch(function(e) {
       return e; // it's OK, we'll just log it
   })
   .done(function(res) {
       logger.log(res);
   });

Você também pode considerar usar .finally()isso.

Bergi
fonte
7
esta é a explicação mais útil que li em alguns dias (e li muito). Não sei explicar como sou grato! :) Eu acho que você deveria enfatizar mais a diferença entre os dois, que .catchvai detectar erros, mesmo dentro da função sucesso .. Pessoalmente, acho isso extremamente errado, como você acabar com um ponto erro de entrada, que pode receber vários erros de várias ações, mas esse é o meu problema. De qualquer forma - obrigado pela informação! Você não tem alguma ferramenta de comunicação on-line que deseja compartilhar para que eu possa pedir mais algumas coisas? : P
Andrey Popov
2
Espero que isso esteja lhe dando mais votos positivos aqui. Definitivamente, uma das melhores explicações de um Promisemecânico importante neste site.
Patrick Roberts
2
.done()não faz parte do padrão, é? Pelo menos o MDN não lista esse método. Isso ajudaria.
ygoe 31/03/19
1
@ygoe De fato. doneé uma coisa do Bluebird que foi basicamente descontinuada pela thendetecção de rejeição sem tratamento.
Bergi 31/03/19
1
apenas uma nota de um daltônico: os diagramas não fazem sentido :)
Benny K
37

Os dois não são completamente idênticos. A diferença é que o primeiro exemplo não captura uma exceção lançada em seu successmanipulador. Portanto, se o seu método retornar apenas promessas resolvidas, como geralmente é o caso, você precisará de um catchmanipulador à direita (ou outro thencom um successparâmetro vazio ). Claro, pode ser que seu thenmanipulador não faça nada que possa falhar potencialmente; nesse caso, usar um parâmetro de 2 thenpode ser bom.

Mas acredito que o ponto do texto ao qual você vinculou thené o mais útil contra os retornos de chamada em sua capacidade de encadear várias etapas assíncronas, e quando você realmente faz isso, a forma de 2 parâmetros de thensutilmente não se comporta exatamente como o esperado , pela razão acima. É particularmente contra-intuitivo quando usado no meio da cadeia.

Como alguém que fez um monte de coisas assíncronas complexas e esbarrou em cantos como esse mais do que gostaria de admitir, eu realmente recomendo evitar esse antipadrão e seguir a abordagem de manipulador separado.

acjay
fonte
18

Observando as vantagens e desvantagens de ambos, podemos fazer um palpite calculado sobre o que é apropriado para a situação. Estas são as duas principais abordagens para implementar promessas. Ambos têm suas vantagens e menos

Abordagem de captura

some_promise_call()
.then(function(res) { logger.log(res) })
.catch(function(err) { logger.log(err) })

Vantagens

  1. Todos os erros são tratados por um bloco de captura.
  2. Até pega alguma exceção no bloco then.
  3. Encadeamento de vários retornos de chamada de sucesso

Desvantagens

  1. Em caso de encadeamento, torna-se difícil mostrar diferentes mensagens de erro.

Abordagem de Sucesso / Erro

some_promise_call()
.then(function success(res) { logger.log(res) },
      function error(err) { logger.log(err) })

Vantagens

  1. Você obtém um controle de erro refinado.
  2. Você pode ter uma função comum de tratamento de erros para várias categorias de erros, como erro db, erro 500, etc.

Desvantagens

  1. Você ainda precisará de outro catchse desejar manipular erros gerados pelo retorno de chamada bem-sucedido
aWebDeveloper
fonte
Para alguém que precisa depurar problemas de produção usando apenas um arquivo de log, prefiro a Abordagem de Sucesso / Erro, pois permite criar uma cadeia de erros causais que pode ser registrada nos limites de saída do seu aplicativo.
Shane Rowatt 07/07
questão. digamos que eu faça uma chamada assíncrona que faça uma de algumas das seguintes coisas: 1) retorna com êxito (código de status 2xx), 2) retorna sem êxito (código 4xx ou 5xx), mas não é rejeitado por si só, 3) ou não retorna ( a conexão com a internet está inativa). Para o caso 1, o retorno de chamada com êxito no .then é atingido. Para o caso 2, o retorno de chamada de erro no .then é atingido. No caso 3, o .catch é chamado. Esta é uma análise correta, certo? O caso 2 é o mais complicado, tecnicamente, 4xx ou 5xx não é uma rejeição, ainda retorna com êxito. Portanto, precisamos lidar com isso dentro do .then. .... O meu entendimento está correto?
Benjamin Hoffman
"No caso 2, o retorno de chamada de erro no .then é atingido. No caso 3, o .catch é chamado. Esta é uma análise correta, certo?" - É assim que a busca funciona
aWebDeveloper 3/17/17
2

Explique simples:

No ES2018

Quando o método catch é chamado com o argumento onRejected, são executadas as seguintes etapas:

  1. Que a promessa seja esse valor.
  2. Retorna ? Invocar (promessa, "então", «indefinido, onRejected»).

que significa:

promise.then(f1).catch(f2)

é igual a

promise.then(f1).then(undefiend, f2)
bitfishxyz
fonte
1

Usar .then().catch()permite ativar o Encadeamento de promessa, necessário para realizar um fluxo de trabalho. Pode ser necessário ler algumas informações do banco de dados, depois passar para uma API assíncrona e manipular a resposta. Você pode enviar a resposta de volta ao banco de dados. Lidar com todos esses fluxos de trabalho com o seu conceito é factível, mas é muito difícil de gerenciar. A melhor solução será a then().then().then().then().catch()que recebe todos os erros em apenas uma captura e permite manter a manutenção do código.

Jayant Varshney
fonte
0

Usando then()e catch()ajudando a cadeia de sucesso e falha manipulador na promessa. catch()trabalha sob promessa devolvida por then(). Ele lida com,

  1. Se a promessa foi rejeitada. Veja # 3 na imagem
  2. Se ocorreu um erro no manipulador de sucesso de then (), entre os números de linha 4 a 7 abaixo. Consulte o item 2.a na imagem (a falha de retorno de chamada then()não lida com isso.)
  3. Se ocorreu um erro no manipulador de falhas de then (), número da linha 8 abaixo. Veja # 3.b na imagem.

1. let promiseRef: Promise = this. aTimetakingTask (false); 2. promiseRef 3. .then( 4. (result) => { 5. /* successfully, resolved promise. 6. Work on data here */ 7. }, 8. (error) => console.log(error) 9. ) 10. .catch( (e) => { 11. /* successfully, resolved promise. 12. Work on data here */ 13. });

insira a descrição da imagem aqui

Nota : Muitas vezes, o manipulador de falhas pode não ser definido se catch()já estiver gravado. EDIT: reject()resultar em invocação catch()apenas se o manipulador de erros nãothen() estiver definido. Observe # 3 na imagem para o . É chamado quando o manipulador nas linhas 8 e 9 não está definido.catch()

Faz sentido porque a promessa retornada por then()não tem um erro se um retorno de chamada está cuidando disso.

VenCKi
fonte
A seta do número 3 para o catchretorno de chamada parece errada.
Bergi 15/09/19
Obrigado! Com um retorno de chamada de erro definido em then (), ele não é chamado (linhas 8 e 9 no snippet de código). # 3 invoca uma das duas setas. Faz sentido porque a promessa retornada até então () não tem um erro se um retorno de chamada estiver cuidando disso. Editou a resposta!
VenCKi 15/09/19
-1

Em vez de palavras, bom exemplo. Código a seguir (se a primeira promessa for resolvida):

Promise.resolve()
.then
(
  () => { throw new Error('Error occurs'); },
  err => console.log('This error is caught:', err)
);

é idêntico a:

Promise.resolve()
.catch
(
  err => console.log('This error is caught:', err)
)
.then
(
  () => { throw new Error('Error occurs'); }
)

Mas com a primeira promessa rejeitada, isso não é idêntico:

Promise.reject()
.then
(
  () => { throw new Error('Error occurs'); },
  err => console.log('This error is caught:', err)
);

Promise.reject()
.catch
(
  err => console.log('This error is caught:', err)
)
.then
(
  () => { throw new Error('Error occurs'); }
)
ktretyak
fonte
4
Isso não faz sentido, você pode remover esta resposta? É enganador e distrai a resposta correta.
Andy Ray
@ AndyRay, isso não faz sentido na aplicação real, mas faz sentido entender o trabalho das promessas.
Ktretyak 15/0518