36
Handle multiple exceptions with try catch and Promise.all
Tools:
- NodeJS: 12.20
- sequelize: ^6.3.5
Scenario: I have to create one Cash Register record in the database and associate it's products and treatments items in another table.
Data Input: Array of objects containing productId, treatmentId and stuff
To help us ensure that all registers gonna be inserted successfully we're going to use the Sequelize's transactions.
The module called CashRegisterService.register() is responsible to create the record in the table CashRegister and delegate the insertion of it's ternaries tables (ProductLines and TreatmentLines).
Let's check how i planned my code in the beginning:
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();
}
}
We can notice one thing: Even though we got an excepetion (related with the ternaries or even though the CashRegister model) we make the .commit(), because while the ternaries functions is running, the code move foward and reach the t.commit() code due to the Javascript's asynchronicity, so it's going to create a CashRegister record in the database but it's going to crash if there's some problem inside the assocation ternaries methods . Let's fix it adding the 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();
}
}
Now i'm getting the error UnhandledPromiseRejectionWarning, and it's happening due i got a list of objects to insert using models CasgRegister, ProductLine and TreatmentLine, and as i told before that JavaScript is async, the commit runs before finish the object's list of insertion in the map function. To fix this problem, we're going to wrap the map function with the Promise function.
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();
}
}
Now we're close, so our code only runs t.commit() after we run all promises in our code snippet inside the map function, and any exception can be handled by the caller. Remember that i make all validations inside the associates... functions, and if somethings is bad, i throw an exception that is handled by the register's caller function. We still have one problem, when we got some exception in the .register() method (our main method), we don't treat this with rollback, so we just have to add a .then() and a .catch().
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;
})
}
}
If you think that there's something confusing, or impacting the understanding, or that i can improve, please i'm going to appreciate your feedback.
See you guys
36