Criando Cadeias de Promises - JavaScript

Uma das vantagens dos promises é a possibilidade de criar cadeias de promises. Anteriormente, vimos a aplicação dos métodos then() e catch() para obter e tratar os resultados e erros de uma operação assíncrona. Ao serem executados, esses métodos geram um novo objeto Promise, para o qual também podemos chamar os métodos then() e catch(), e assim construir uma cadeia de promises. Isso nos permite tratar várias operações assíncronas consecutivamente, uma após a outra.

promise.then(..).then(..).then(..)

O valor retornado da função manipuladora no método then() é passado para a próxima chamada do método then() na cadeia:

const helloPromise = new Promise(function(resolve) {
    resolve("Hello");
});

const worldPromise = helloPromise.then(function(value) {
    // retornando um novo valor
    return value + " World";
});

const programicioPromise = worldPromise.then(function(value) {
    // retornando um novo valor
    return value + " from Programício";
});

programicioPromise.then(function(finalValue) {
    // obtendo o valor final
    console.log(finalValue);    // Hello World from Programício
});

Aqui, para maior clareza, todo o processo foi dividido em promises separadas: helloPromise, worldPromise e programicioPromise.

Cadeia de promise em JavaScript

Vamos examinar cada etapa.

Primeiro, é criado o promise helloPromise:

const helloPromise = new Promise(function(resolve) {
    resolve("Hello");
});

Na operação assíncrona, por meio da chamada resolve("Hello"), o promise é colocado no estado fulfilled, ou seja, a operação foi concluída com sucesso. E o valor "Hello" é passado para fora.

Em seguida, o método then() é chamado no helloPromise:

const worldPromise = helloPromise.then(function(value) {
    // retornando um novo valor
    return value + " World";
});

Como valor do parâmetro value, a função manipuladora recebe a string "Hello" e então retorna a string "Hello World". Essa string pode ser obtida por meio do método then() do novo promise, que é gerado pela chamada helloPromise.then() e que aqui é chamado de worldPromise.

Em seguida, de maneira semelhante, o método then() é chamado no promise worldPromise:

const programicioPromise = programicioPromise.then(function(value) {
    // retornando um novo valor
    return value + " from Programício";
});

Como valor do parâmetro value, a função manipuladora recebe a string "Hello World" e então retorna a string "Hello World from Programício". A chamada worldPromise.then() retorna um novo promise, o programicioPromise.

Na última etapa, o método then() é chamado no promise programicioPromise:

programicioPromise.then(function(finalValue) {
    console.log(finalValue);    // Hello World from Programício
});

Aqui, por meio do parâmetro finalValue, obtemos o valor final, a string "Hello World from Programício", e a exibimos no console. Após isso, a cadeia é concluída.

Para maior concisão e clareza, podemos simplificar a cadeia:

new Promise(resolve => resolve("Hello"))
    .then(value => value + " World")
    .then(value => value + " from Programício")
    .then(finalValue => console.log(finalValue));

Tratamento de Erros

Para tratar erros, adicionamos o método catch() no final da cadeia, que também retorna um objeto Promise. Vejamos um exemplo simples:

function generateNumber(str) {
  return new Promise((resolve, reject) => {
      const parsed = parseInt(str);
      if (isNaN(parsed)) reject("Not a number");
      else resolve(parsed);
  });
}

function printNumber(str) {
  generateNumber(str)
      .then(value => console.log(value))
      .catch(error => console.log(error)); 
}

printNumber("hello"); // Not a number
printNumber("3");   // 3

Neste caso, a função generateNumber() retorna um promise onde tentamos converter um valor em um número. Na função printNumber(), chamamos essa função e, no promise resultante, criamos uma pequena cadeia de métodos then() e catch().

Se a conversão da string para número no promise for bem-sucedida, o número convertido é passado para a função resolve():

else resolve(parsed)

Neste caso, ao receber este resultado, o método then() é acionado, exibindo o valor obtido no console:

.then(value => console.log(value))

O método catch() não é executado na ausência de erros.

No entanto, se o valor passado não puder ser convertido em número, o seguinte é executado no promise:

if (isNaN(parsed)) reject("Not a number");

Neste caso, o método then() é ignorado e a execução passa para:

.catch(error => console.log(error));

Tratando Erros na Cadeia de Promises

Agora vamos complicar a cadeia. Suponha que vários promises sejam executados consecutivamente:

function generateNumber(str) {
  return new Promise((resolve, reject) => {
      const parsed = parseInt(str);
      if (isNaN(parsed)) reject("Not a number");
      else resolve(parsed);
  });
}

function printNumber(str) {
  generateNumber(str)
      .then(value => {
          if (value === 4) throw "Número azarado";
          return value * value;
      })
      .then(finalValue => console.log(`Result: ${finalValue}`))
      .catch(error => console.error(error));
}

printNumber("rty"); // Not a number
printNumber("3");   // Result: 9
printNumber("4");   // Número azarado
printNumber("5");   // Result: 25

Aqui, para simplificar, todo o código está na função generateNumber(), que cria a cadeia de promises. Nessa cadeia, recebemos um valor externo, tentamos convertê-lo em um número, calculamos seu quadrado e o exibimos no console. No final, temos o método catch(), que recebe um manipulador de erros para exibir a mensagem de erro no console. Se em algum momento da cadeia de promises ocorrer um erro (por exemplo, devido a um throw() ou reject()), todas as chamadas subsequentes de then() que apenas tratam o valor são ignoradas, e a execução passa para o método catch().

Por exemplo, ao chamar a função printNumber() com diferentes dados de entrada:

printNumber("rty");

Recebemos a mensagem "Not a number" no console, pois a string "rty" não pode ser convertida em um número.

Retornando um Promise de catch

Vale ressaltar que, como catch() retorna um objeto Promise, podemos continuar a cadeia após ele:

function generateNumber(str) {
  return new Promise((resolve, reject) => {
      const parsed = parseInt(str);
      if (isNaN(parsed)) reject("Not a number");
      else resolve(parsed);
  });
}

function printNumber(str) {
  generateNumber(str)
      .then(value => value * value)
      .then(value => console.log(`Result: ${value}`))
      .catch(error => console.error(error))
      .then(() => console.log("Work has been done"));
}

printNumber("3");   
// Result: 9
// Work has been done

O método then() após catch() será chamado mesmo que não ocorram erros e o próprio método catch() não seja executado.

Além disso, podemos passar um valor da função manipuladora de erros no catch() para o método then() subsequente:

function generateNumber(str) {
  return new Promise((resolve, reject) => {
      const parsed = parseInt(str);
      if (isNaN(parsed)) reject("Not a number");
      else resolve(parsed);
  });
}

function printNumber(str) {
  generateNumber(str)
      .then(value => value * value)
      .then(value => console.log(`Result: ${value}`))
      .catch(error => {
          console.log(error);
          return 0;
      })
      .then(value => console.log("Status code:", value));
}

printNumber("ert3");    // Not a number
                      // Status code: 0

Método finally

Além dos métodos then() e catch(), o objeto Promise também fornece o método finally() para o tratamento do resultado. Esse método é executado no final da cadeia de promises, independentemente se ocorreu um erro ou se o promise foi concluído com sucesso.

O método finally() aceita uma função como parâmetro, que executa algumas ações finais no processamento do promise:

function generateNumber(str) {
  return new Promise((resolve, reject) => {
      const parsed = parseInt(str);
      if (isNaN(parsed)) reject("Not a number");
      else resolve(parsed);
  });
}

function printNumber(str) {
  generateNumber(str)
      .then(value => console.log(value))
      .catch(error => console.log(error))
      .finally(() => console.log("End"));
}

printNumber("3");
printNumber("hi");

Aqui, chamamos duas vezes o promise retornado pela função generateNumber. Em um caso, a string é convertida com sucesso em um número, e no outro, ocorre um erro. No entanto, independentemente da ausência ou presença de erro, o método finally() será executado em ambos os casos, exibindo "End" no console.

O método finally() retorna um objeto Promise, permitindo continuar a cadeia após ele:

function generateNumber(str) {
  return new Promise((resolve, reject) => {
      const parsed = parseInt(str);
      if (isNaN(parsed)) reject("Not a number");
      else resolve(parsed);
  });
}

function printNumber(str) {
  generateNumber(str)
      .then(value => console.log(value))
      .catch(error => console.log(error))
      .finally(() => console.log("Execução do promise concluída"))
      .then(() => console.log("O promise ainda está funcionando"));
}

printNumber("3");

Saída no console:

3
Execução do promise concluída
O promise ainda está funcionando

Vale destacar que é possível passar dados para o método then que vem depois do finally. No entanto, esses dados não são passados pelo método finally(), mas sim pelo método then() ou catch() anterior:

function generateNumber(str) {
  return new Promise((resolve, reject) => {
      const parsed = parseInt(str);
      if (isNaN(parsed)) reject("Not a number");
      else resolve(parsed);
  });
}

function printNumber(str) {
  generateNumber(str)
      .then(value => {
          console.log(value);
          return "hello from then";
      })
      .catch(error => {
          console.log(error);
          return "hello from catch";
      })
      .finally(() => {
          console.log("End");
          return "hello from finally";
      })
      .then(message => console.log(message));
}

printNumber("3");

Saída no console:

3
End
hello from then
Política de Privacidade

Copyright © www.programicio.com Todos os direitos reservados

É proibida a reprodução do conteúdo desta página sem autorização prévia do autor.

Contato: programicio@gmail.com