Exceções em cadeias de Promise em JavaScript

Suponha que, por algum motivo, nossa promise seja rejeitada com um erro:

javascriptlet promise = new Promise(function(resolve, reject) {
  setTimeout(function() {
    reject('error');
  }, 3000);
});

Neste caso, a execução irá imediatamente para o then que possui um tratador de erro, ou para o primeiro catch, o que aparecer primeiro.

Aqui está um exemplo da primeira situação:

javascriptpromise.then(
  function(result) {
    return result + '1';
  }
).then(
  function(result) {
    return result + '2';
  },
  function(error) {
    // a execução irá diretamente para aqui
  }
).then(
  function(result) {
    console.log(result);
  }
);

Aqui está um exemplo da segunda situação:

javascriptpromise.then(
  function(result) {
    return result + '1';
  }
).then(
  function(result) {
    return result + '2';
  }
).catch(
  function(error) {
    // a execução irá diretamente para aqui
  }
).then(
  function(result) {
    console.log(result);
  }
);

O tratador de erro tem duas opções de ação: se ele conseguiu lidar com a situação excepcional, pode retornar um resultado via return e a execução continuará pela cadeia. Se ele não conseguiu lidar com o erro, pode ou não retornar nada, ou lançar uma exceção via throw. Neste caso, a execução passará para o próximo tratador de erro (em then ou catch - o que vier primeiro).

Geralmente, todos os erros da cadeia são capturados em um único lugar: um catch é colocado no final da cadeia:

javascriptpromise.then(
  function(result) {
    return result + '1';
  }
).then(
  function(result) {
    return result + '2';
  }
).catch(
  function(error) {
    // chegaremos aqui em caso de erro
  }
);

Neste caso, a exceção pode originar-se na própria promise, ou ser lançada via throw em qualquer elo da cadeia:

javascriptpromise.then(
  function(result) {
    return result + '1';
  }
).then(
  function(result) {
    if (tudoBem) {
      return result + '2';
    } else {
      throw new Error('erro'); // vamos para o tratador mais próximo
    }
  }
)
.then(
  function(result) {
    return result + '3';
  }
).catch(
  function(error) {
    // o tratador mais próximo
  }
);

Observe que o catch é necessário precisamente para diagnosticar o erro: ele é solucionável ou não. Se o erro for solucionável, o catch deve passar sua solução para o then seguinte. E se não for solucionável (ou se este catch simplesmente não souber como resolvê-lo), devemos ou não retornar nada ou lançar uma exceção:

javascriptpromise.then(
  function(result) {
    return result + '1';
  }
).then(
  function(result) {
    return result + '2';
  }
).catch(
  function(error) {
    if (erroSolucionavel) {
      return 'dados'; // enviamos para o próximo then
    } else {
      // não retornamos nada ou lançamos uma exceção
    }
  }
).then(
  function(result) {
    // aqui resolvemos o erro
  }
);