Como lidar com várias exceções usando try catch e Promise.all
Ferramentas:
- NodeJS: 12.20
- sequelize: ^6.3.5
Cenário: Em um projeto de gerenciamento de Salão de beleza, preciso popular a tabela CashRegister na base de dados e associar os produtos e tratamentos em outras tabelas (tabelas ternárias).
Dados de entrada: Array de objetos contendo productId, treatmentId e alguns outros dados.
Para garantir que todos os registros serão inseridos com sucesso na base, estaremos utilizando transações Sequelize
O módulo chamado CashRegisterService.register() é responsável por criar o registro na tabela CashRegister e delegar a inserção nas tabelas ternárias associadas. (ProductLines and TreatmentLines).
Vamos verificar como eu planejei meu código no início:
const CashRegisterService = {
async register(data) {
const t = await sequelize.transaction();
data.map(register => {
let cashRegister = await CashRegister.upsert(register, {transaction: t})
if(!cashRegister) {
throw new Error('Error when trying populate Cash Register')
}
this.associateProductLine(cashRegister[0], {...register, t});
this.associateTreatmentLine(cashRegister[0], {...register, t});
})
t.commit();
}
}
Podemos perceber uma coisa: Mesmo quando temos uma exceção (que pode ser gerada nas tabelas ternárias ou até mesmo da nossa model CashRegister) nós fazemos o .commit() mesmo assim, porque enquanto as funções ternárias estão rodando, o código segue e alcança o método t.commit() devido a assincronicidade do Javascript, então isso vai criar um registro na tabela CashRegister, mas teremos uma inconsistência se houver algum problema nos métodos relacionados com as associações ternárias. Vamos arrumar tentando adicionar um await:
const CashRegisterService = {
async register(data) {
const t = await sequelize.transaction();
data.map(register => {
let cashRegister = await CashRegister.upsert(register, {transaction: t})
if(!cashRegister) {
throw new Error('Error when trying populate Cash Register')
}
await this.associateProductLine(cashRegister[0], {...register, t});
await this.associateTreatmentLine(cashRegister[0], {...register, t});
})
t.commit();
}
}
Agora temos o erro UnhandledPromiseRejectionWarning, e está acontecendo porque meu método possui a lista de objetos para inserir utilizandos as models CasgRegister, ProductLine e TreatmentLine, e como eu disse anteriormente que o Javascript é assíncrono, o commit roda antes de finalizar a inserção de todos os objetos da lista no método data.map(). Para arrumar esse problema, vamos colocar esse método data.map() dentro de uma função Promise.
const CashRegisterService = {
async register(data) {
const t = await sequelize.transaction();
await Promise.all(data.map(register => {
let cashRegister = await CashRegister.upsert(register, {transaction: t})
if(!cashRegister) {
throw new Error('Error when trying populate Cash Register')
}
await this.associateProductLine(cashRegister[0], {...register, t});
await this.associateTreatmentLine(cashRegister[0], {...register, t});
}))
t.commit();
}
}
Estamos quase lá, o nosso código roda t.commit() somente depois se executar todas as funções dentro da Promise e qualquer exceção lançada pode ser tratada por quem está fazendo a chamda das funções internas. Lembre-se que eu fiz todas as validações dentro das funções que se iniciam com associates..., e se algo correr mal, a exceção lançada será tratada pela nossa funçãos register. Ainda temos um problema, quando nós temos uma exceção no método .register() (nosso método principal), nós não tratamos com o rollback (ou seja, desfazer as alterações na base de dados), então temos que adicionar is métodos .then() e .catch() para organizarmos melhor como fazemos esse tratamento.
const CashRegisterService = {
async register(data) {
const t = await sequelize.transaction();
await Promise.all(data.map(register => {
let cashRegister = await CashRegister.upsert(register, {transaction: t})
if(!cashRegister) {
throw new Error('Error when trying populate Cash Register')
}
await this.associateProductLine(cashRegister[0], register);
await this.associateTreatmentLine(cashRegister[0], {...register, t});
})).then(async result => {
await t.commit();
}).catch(async error => {
await t.rollback();
throw error;
})
}
}
É isso pessoal, se tiverem alguma crítica, sugestão de melhorias ou dúvidas, mandem a pergunta :-)
Até mais