Criando Cadeias de Promises em JavaScript
Uma das vantagens das 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
.
Vamos examinar cada etapa.
Primeiro, é criado a promise helloPromise
:
const helloPromise = new Promise(function (resolve) {
resolve("Hello");
});
Na operação assíncrona, por meio da chamada resolve("Hello")
, a promise é colocada 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 uma 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 uma 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 a promise foi concluída 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 a promise retornada 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("A promise ainda está funcionando"));
}
printNumber("3");
Saída no console:
3 Execução do promise concluída A 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