I am creating an API with CRUD operations for User resource. Because TypeORM doesn't return the created user after inserting it to the database, I perform a 'findOne' request to get it back providing the userName.
In order to do this I created a transaction in my UserService using QueryRunner. It looks like this :
Controller
@Post()
create(@Body() user: User) {
return this.userService
.createUser(user)
.then((user) => {
return user;
})
.catch(() => {
throw new InternalServerErrorException('Could not create user');
});
}
Service
createUser(user: User): Promise<User | void> {
const queryRunner = this.connection.createQueryRunner();
return queryRunner
.connect()
.then(() => {
queryRunner
.startTransaction()
.then(() => {
queryRunner.manager
.save(User, user)
.then((user) => {
queryRunner.manager
.findOne(User, {
username: user.username,
})
.then((user) => {
queryRunner
.commitTransaction()
.then(() => {
return Promise.resolve(user);
})
.catch((error) => {
console.log('Could not commit transaction : ', error);
return Promise.reject();
});
})
.catch((error) => {
console.log(
`Could not get user after insert, username : ${user.username}, error : ${error}`,
);
return Promise.reject();
});
})
.catch((error) => {
console.log(
'Could not insert user into the database : ',
error,
);
queryRunner
.rollbackTransaction()
.then(() => {
console.log('Rolled back transaction');
return Promise.resolve();
})
.catch((error) => {
console.log('Could not rollback transaction : ', error);
return Promise.reject();
});
});
})
.catch((error) => {
console.log('Could not start transaction : ', error);
return Promise.reject();
});
})
.catch((error) => {
console.log('Could not connect to database : ', error);
return Promise.reject();
})
.finally(() => {
queryRunner
.release()
.then((user) => {
console.log('Released query runner for User transaction');
return Promise.resolve(user);
})
.catch((error) => {
console.log('Could not release queryRunner : ', error);
return Promise.reject();
});
});
}
There are many issues in this code and I am aware of it, but I am new to the javascript world and don't know how to do better:
- I looked on the internet and some people call what I am doing (nesting promises) a pyramid of doom and that it is an anti-pattern, but I don't know how to do otherwise.
- When I run my code and do a request to create the user, the finally() block is being called before then() and thus release my queryRunner before I do my transaction. Can anyone tell me why?
- In order to fix the issue I removed the finally() and did the release inside then() and catch() but what happened then is that my function returned after calling "startTransaction" and didn't do the then() before, so what happens is that I get back to the controller and return a response to the user before finishing my UserService function (createUser).
I hope that you have enough information to help me, but I will be glad to add more details if needed.
Thank you,
CodePudding user response:
regards your questions
1- try to convert your function to async
and use async/await
this will give you a clearer look to the logic you are trying to implement
2- finally
being called before then
probably means an error has occurred, try logging the error in the catch
and debug it
this is not the answer, I'm just showing you an alternative way to the promises pyramid, I hope I got your logic right
async function createUser(user): Promise<any> {
const queryRunner = this.connection.createQueryRunner();
await queryRunner.connect().catch(throwErr("couldn't connect") )
await queryRunner.startTransaction().catch(throwErr('could not start transaction'))
const User = await queryRunner.manager.save(user).catch(throwErr("could not save user") )
await queryRunner.commitTransaction().catch(throwErr("could not complete transaction"))
await queryRunner.release().catch(throwErr('Could not release queryRunner'))
const foundedUser = await queryRunner.manager.findOne(User, {
username: user.username,
}).catch(throwErr("Could not connect to database"))
return foundedUser
}
function throwErr(message){
return (err)=> {throw new Error(message)}
}
CodePudding user response:
Thanks to scr2em and David Callanan I changed my code from nested promises to async/await and now it's working, this is what I did:
async createUser(user: User): Promise<User | void> {
const action = `(Create user : ' ${user.username}) `;
const queryRunner = this.connection.createQueryRunner();
await queryRunner.connect().catch((error) => {
console.log(action 'Could not connect to database : ', error);
return Promise.reject();
});
await queryRunner.startTransaction().catch((error) => {
console.log(action 'Could not start transaction : ', error);
return Promise.reject();
});
await queryRunner.manager.save(User, user).catch(async (error) => {
console.log(action 'Could not insert user into the database : ', error);
await queryRunner.rollbackTransaction().catch((error) => {
console.log(action 'Could not rollback transaction : ', error);
return Promise.reject();
});
return Promise.reject();
});
const userResult = await queryRunner.manager
.findOne(User, { username: user.username })
.catch(async (error) => {
console.log(
action
`Could not get user after insert, username : ${user.username}, error : ${error}`,
);
await queryRunner.rollbackTransaction().catch((error) => {
console.log(action 'Could not rollback transaction : ', error);
return Promise.reject();
});
return Promise.reject();
});
await queryRunner.commitTransaction().catch((error) => {
console.log(action 'Could not commit transaction : ', error);
return Promise.reject();
});
await queryRunner.release().catch((error) => {
console.log(action 'Could not release queryRunner : ', error);
return Promise.reject();
});
return Promise.resolve(userResult);
}
Hope that it will help someone else.