Colocação da captura ANTES e DEPOIS, em seguida

103

Tenho dificuldade em entender a diferença entre colocar .catchANTES e DEPOIS em uma promessa aninhada.

Alternativa 1:

test1Async(10).then((res) => {
  return test2Async(22)
    .then((res) => {
      return test3Async(100);
    }).catch((err) => {
      throw "ERROR AFTER THEN";
    });
}).then((res) => {
  console.log(res);
}).catch((err) => {
  console.log(err);
});

Alternativa 2:

test1Async(10).then((res) => {
   return test2Async(22)
     .catch((err) => {
        throw "ERROR BEFORE THEN";
      })
      .then((res) => {
        return test3Async(100);
      });
  }).then((res) => {
    console.log(res);
  }).catch((err) => {
    console.log(err);
  });

O comportamento de cada função é o seguinte: test1 falha se número for <0test2 falha se número for > 10e test3 falha se número não for 100. Neste caso, test2 está apenas falhando.

Tentei rodar e fazer o test2Async falhar, tanto ANTES quanto DEPOIS então se comporta da mesma forma e que não está executando o test3Async. Alguém pode me explicar a principal diferença para colocar a captura em lugares diferentes?

Em cada função eu console.log('Running test X')para verificar se ela é executada.

Esta questão surge devido ao tópico anterior que publiquei, Como transformar o retorno de chamada aninhado em promessa? . Acho que é um problema diferente e vale a pena postar outro tópico.

Zanko
fonte
Ambos .then e .catch podem alterar a promessa ... então não tenho certeza de onde vem o mal-entendido. Se você colocar catch antes de .then, ele pegará rejeições que aconteceram antes de .then e o .then executará retornos de chamada concluídos / com falha com base no que acontece dentro de .catch e vice-versa quando você os trocar.
Kevin B
Desculpe se minha pergunta não foi clara. Mas, neste caso, como eu disse, ambos os casos se comportam da mesma forma, então não consigo ver a diferença. Você pode me dizer quando colocamos catch ANTES e quando decidimos colocá-lo DEPOIS então? colocá-lo depois parece muito intuitivo e comum. Só não sei por que às vezes colocamos antes
Zanko
Se eles executam o mesmo, é simplesmente porque o que cada um faz não está alterando o resultado neste caso específico. Uma pequena mudança em qualquer um deles pode alterar o resultado.
Kevin B
O que quer dizer "alterando o resultado". Desculpe, estou realmente confuso haha
Zanko
Por exemplo, se em vez de lançar um erro você simplesmente não fizesse nada, a promessa mudaria de rejeitada para resolvida. Isso obviamente alteraria o resultado, porque a promessa agora é uma promessa resolvida, em vez de rejeitada. (a menos que já tenha sido resolvido, caso em que a captura não teria ocorrido de qualquer maneira)
Kevin B

Respostas:

237

Então, basicamente você está perguntando qual é a diferença entre esses dois (onde pestá uma promessa criada a partir de algum código anterior):

return p.then(...).catch(...);

e

return p.catch(...).then(...);

Existem diferenças quando p resolve ou rejeita, mas se essas diferenças importam ou não depende do que o código dentro dos manipuladores .then()ou .catch()faz.

O que acontece quando presolve:

No primeiro esquema, quando presolve, o .then()manipulador é chamado. Se esse .then()manipulador retornar um valor ou outra promessa que eventualmente seja resolvida, o .catch()manipulador será ignorado. Mas, se o .then()manipulador lança ou retorna uma promessa que eventualmente rejeita, então o .catch()manipulador executará para uma rejeição na promessa original p, mas também para um erro que ocorre no .then()manipulador.

No segundo esquema, quando presolve, o .then()manipulador é chamado. Se esse .then()manipulador lança ou retorna uma promessa que eventualmente rejeita, então o .catch()manipulador não pode capturar isso porque está antes dele na cadeia.

Então, essa é a diferença # 1. Se o .catch()manipulador for AFTER, ele também poderá detectar erros dentro do .then()manipulador.

O que acontece quando prejeita:

Agora, no primeiro esquema, se a promessa for prejeitada, o .then()manipulador será ignorado e o .catch()manipulador será chamado conforme o esperado. O que você faz no .catch()manipulador determina o que é retornado como o resultado final. Se você apenas retornar um valor do .catch()manipulador ou retornar uma promessa que eventualmente é resolvida, a cadeia de promessas muda para o estado resolvido porque você "tratou" o erro e retornou normalmente. Se você lançar ou retornar uma promessa rejeitada no .catch()manipulador, a promessa retornada permanecerá rejeitada.

No segundo esquema, se a promessa for prejeitada, o .catch()manipulador será chamado. Se você retornar um valor normal ou uma promessa que eventualmente seja resolvida do .catch()manipulador ("manipulando" assim o erro), a cadeia de promessas muda para o estado resolvido e o .then()manipulador após o .catch()será chamado.

Então essa é a diferença # 2. Se o .catch()manipulador for ANTES, ele pode manipular o erro e permitir que o .then()manipulador ainda seja chamado.

Quando usar qual:

Use o primeiro esquema se desejar apenas um .catch()manipulador que possa detectar erros na promessa original pou no .then()manipulador e uma rejeição de pdeve ignorar o .then()manipulador.

Use o segundo esquema se quiser ser capaz de detectar erros na promessa original pe talvez (dependendo das condições), permitir que a cadeia de promessa continue como resolvida, executando assim o .then()manipulador.

A outra opção

Há uma outra opção para usar os dois retornos de chamada que você pode passar para .then():

 p.then(fn1, fn2)

Isso garante que apenas um de fn1ou fn2será chamado. Se presolver, então fn1será chamado. Se prejeitar, então fn2será chamado. Nenhuma mudança de resultado fn1pode fazer com que fn2seja chamado ou vice-versa. Portanto, se você deseja ter certeza absoluta de que apenas um de seus dois manipuladores é chamado, independentemente do que acontece nos próprios manipuladores, você pode usar p.then(fn1, fn2).

jfriend00
fonte
17
A pergunta é especificamente sobre a ordem de .then()e .catch(), que você responde. Além disso, você dá algumas dicas de quando usar essa ordem, onde acho apropriado mencionar uma terceira opção, ou seja, passar o manipulador de sucesso e de erro para .then () . Nesse caso, no máximo, um manipulador será chamado.
ArneHugo
7
@ArneHugo - Boa sugestão. Eu adicionei.
jfriend00
Portanto, durante o encadeamento de promessas, podemos escrever .catch .catch .then tipos de cenários?
Kapil Raghuwanshi de
@KapilRaghuwanshi, sim, você pode usá-lo para passar um valor padrão em caso de falha. ie Promise.reject(new Error("F")).then(x => x).catch(e => {console.log(e); return [1]}).then(console.log)e Promise.resolve([2]).then(x => x).catch(e => [1]).then(console.log)
CervEd
1
@DmitryShvedov - Como eu imaginei, isso está errado .then(this.setState({isModalOpen: false})). Você não está passando uma referência de função para .then()para que o código nos parênteses seja executado imediatamente (antes que a promessa seja resolvida). Deve ser .then(() => this.setState({isModalOpen: false})).
jfriend00
31

A resposta de jfriend00 é excelente, mas achei que seria uma boa ideia adicionar o código síncrono análogo.

return p.then(...).catch(...);

é semelhante ao síncrono:

try {
  iMightThrow() // like `p`
  then()
} catch (err) {
  handleCatch()
}

Se iMightThrow()não jogar, then()será chamado. Se ele lançar (ou se then()ele próprio lançar), handleCatch()será chamado. Observe como o catchbloco não tem controle sobre se é ou não thenchamado.

Por outro lado,

return p.catch(...).then(...);

é semelhante ao síncrono:

try {
  iMightThrow()
} catch (err) {
  handleCatch()
}

then()

Neste caso, se iMightThrow()não lançar, então then()executará. Se lançar, então caberia handleCatch()a decidir se then()é chamado, porque se handleCatch()relançar, então then()não será chamado, já que a exceção será lançada para o chamador imediatamente. Se handleCatch()puder lidar com o problema normalmente, then()será chamado.

Akivajgordon
fonte
esta é uma boa explicação, mas você poderia embrulhar o órfão then()em umfinally{...}
tyskr
2
@ 82Tuskers, tem certeza? Se eu colocar then()no finally{...}, não seria incorretamente ser chamado mesmo se handleCatch()joga? Tenha em mente que meu objetivo era mostrar código síncrono análogo, não sugerir maneiras diferentes de lidar com exceções
akivajgordon
Então, se quisermos lidar com todos os casos, mas ainda encadear .then () seria melhor usar .then (fazer algo) .catch (logar erro e atualizar o estado) .then (fazer outra coisa) .catch (logar erro) onde tentamos capturar em todos os pontos, mas também continuamos a executar as instruções?
anna