I've read that having an async inside a Promise is anti-pattern for async/await. The code below works, but I am curious how else to achieve the same result without having async in Promise.
If I remove it, the linter would tell how I can't use await in my mongodb query. If I remove the await in the mongodb query, then it wouldn't wait for the result.
export const getEmployees = (companyId) => {
return new Promise(async (resolve, reject) => {
const employees = await Employees.find(
{ companyId },
);
// other logic here...
resolve({
employees,
});
});
Thanks.
async functions automatically return Promises already, which resolve with whatever expression is eventually returned. Simply make getEmployees an async function:
export const getEmployees = async (companyId) => {
const employees = await Employees.find(
{ companyId },
);
// other logic here...
return { employees };
};
(but make sure to catch in the consumer of getEmployees just in case there's an error)
As #CertainPerformance answered, that is perfect way to retrieve data from mongoDB using async/await, I would like to add some more information about how to handle errors in this case for correctness of the system, and better error handle to return better status to the client about his request.
I'd say it , you usually want to catch all exceptions from async/await call.
try {
const employees = await Employees.find({
companyId
});
// You can add more logic here before return the data.
return {
employees
};
} catch (error) {
console.error(error);
}
Now let's check the ways we can handle our errors that might occur.
Handle error inside error scope.
Assign a default value to the variable in the catch block.
Inspect error instance and act accordingly.
This is the most common way to handle errors in those cases and most elegant way in my opinion.
Handle error inside error scope:
export const getEmployees = async (companyId) => {
try {
const employees = await Employees.find({
companyId
});
// You can add more logic here before return the data.
return {
employees
};
} catch (error) {
console.error(error);
}
};
Assign a default value to the variable in the catch block:
export const getEmployees = async (companyId) => {
let employees;
try {
employees = await Employees.find({
companyId
});
// You can add more logic here before return the data.
employees = employees;
} catch (error) {
console.error(error);
}
if (employees) { // We received the data successfully.
console.log(employees)
// Business logic goes here.
}
return employees;
};
Inspect error instance and act accordingly:
export const getEmployees = async (companyId) => {
try {
const employees = await Employees.find({
companyId
});
// You can add more logic here before return the data.
return {
employees
};
} catch (error) {
if (error instanceof ConnectionError) {
console.error(error);
} else {
throw error;
}
}
};
Some more explanations about async await and more useful methods that you can find in those answers.
How run async / await in parallel in Javascript
Related
I'd like to know if it's possible to make 2 API calls inside a loader function if I am using react-router 6. My ideas was to create an object based on these 2 calls and destruct the object in the rendering component like this:
function MainComponent (){
const {data , reservation} = useRouteLoaderData('room-details');
..
..
}
export default MainComponent;
export async function loader({request, params}) {
const id = params.roomId;
const response = await fetch ('http://localhost:8080/rooms/' + id);
const response2 = await fetch('http://localhost:8080/rooms/reservation/' + id)
const megaResponse = {
data: response, //i tried data:{respose} It ain't work
reservation: response2,
};
if (!response.ok) {
throw json({message: 'Something Wrong'}, {status: 500});
}
else {
return megaResponse;
}
}
But i have no success output.
I'd really want to make these 2 call in one place, otherwise I will have to use useEffect in a child component. Not a good Idea I think.
Thanks
I suspect you are not returning the unpacked response, i.e. JSON. I suggest surrounding the asynchronous code in a try/catch and simply try to process the requests/responses. Unpack the JSON value from the response objects. Since it doesn't appear the requests are dependent on one another I recommend loading them into an array of Promises that can be run concurrently and awaited as a whole. If during any part of the processing a Promise is rejected or an exception thrown, the catch block will return the JSON error response to the UI, otherwise, the { data, reservation } object is returned.
const loader = async ({ request, params }) => {
const { roomId } = params;
try {
const [data, reservation] = await Promise.all([
fetch("http://localhost:8080/rooms/" + roomId),
fetch("http://localhost:8080/rooms/reservaton/" + roomId)
]).then((responses) => responses.map((response) => response.json()));
return { data, reservation };
} catch {
throw json({ message: "Something Wrong" }, { status: 500 });
}
};
I found the solution, I tried it and it worked. It is as follow:
function MainComponent (){
const [data , reservation] = useRouteLoaderData('room-details');
..
..
}
export default MainComponent;
export async function loader({request, params}) {
const id = params.roomId;
return Promise.all([
fetch ('http://localhost:8080/rooms/' + id),
fetch('http://localhost:8080/rooms/reservation/' + id)
])
.then(
([data, reservation]) =>
Promise.all([data.json(), reservation.json()]),
error => {throw json({message: 'Something Wrong'}, {status: 500});}
)
.then(([data, reservation]) => {
return [data, reservation];
});
}
Thanks
I'm a newbie studying coding.
I tried to mock mysql database query, so I followed below code(https://techhelpnotes.com/node-js-mock-database-jest-using-ts-jest-utils/)
//database.js
const getDbConnection = () => {
const pool = mysql.createPool(DB_CONFIG);
return {
query : function (sql) {
return util.promisify(pool.query).call(pool, sql);
};
//controller.js
try {
let result = await db.query(sql);
res.status(200).json(result);
} catch (error) {
console.log(DB error, error);
}
It worked for me, but I thought I could return the query with await by using it in databse.js like the one below
query : async function(sql) {
try {
return await util.promisify(pool.query).call(pool, sql);
} catch (error) {
console.log(DB error, error);
}
}
so I thought I can use query function without error handling
let result = await db.query(sql);
But it doesn't work. What difference is there between the two codes that makes the above code work and the below now??
Many thanks!
I'm trying to use database transaction to create a Page record however I'm getting Variable 'createdPage' is used before being assigned even though this.pagesService.create() only returns Page and it will throw error if something goes wrong so program can be sure that createdPage is set if no exception is thrown. So why I'm getting this error?
#Post('')
async create(
#Body() body: PageCreateDto,
): Promise<Page> {
let createdPage: Page;
try {
await this.database.transaction(async trx => {
createdPage = await this.pagesService.create(body, trx);
});
} catch (error) {
throw new InternalServerErrorException('unable to create page');
}
return createdPage;
}
The problem is that the function you pass into the transaction call doesn't get run synchronously and so you can't be sure that createdPage is actually assigned when you return it. You could solve this by creating a promise.
#Post('')
async create(#Body() body: PageCreateDto): Promise<Page> {
return new Promise<Page>((resolve, reject) => {
try {
await this.database.transaction(trx => this.pagesService
.create(body, trx)
.then(resolve));
} catch (error) {
reject(new InternalServerErrorException('unable to create page'));
}
});
}
Returning it inside arrow function solved the issue:
#Post('')
async create(
#Body() body: PageCreateDto,
): Promise<Page> {
let createdPage: Page;
try {
createdPage = await this.database.transaction(async trx => {
return this.pagesService.create(body, trx);
});
} catch (error) {
throw new InternalServerErrorException('unable to create page');
}
return createdPage;
}
I'd like to convert this Ember route action to use ES2017 async / await. Can someone please explain what this would look like?
Per spec, I already added: babel: { includePolyfill: true } to my ember-cli-build.js file:
save() {
let tenant = this.modelFor(this.routeName).tenant;
let app = this.modelFor(this.routeName).app;
return tenant.save().then(()=> {
return tenant.get('app').save({ adapterOptions: { tenantId: tenant.id }}).then(() => {
this.transitionTo('tenants.tenant.info', tenant.id);
}).catch((error) => {
tenant.get('app').rollback();
throw error;
});
}).catch((error) => {
tenant.rollback();
throw error;
});
}
Your code, converted to async/await:
async save() {
let tenant = this.modelFor(this.routeName).tenant;
let app = this.modelFor(this.routeName).app;
try {
await tenant.save();
try {
await tenant.get('app').save({ adapterOptions: { tenantId: tenant.id }});
this.transitionTo('tenants.tenant.info', tenant.id);
} catch (error) {
tenant.get('app').rollback();
throw error;
}
} catch (error) {
tenant.rollback();
throw error;
}
}
To convert from promises, you add the await keyword to method calls that return promises. Everything that you place in the then method of the promise you can simply put after the await statement.
The promises' catch methods convert to regular try/catch blocks.
Very similar to the answer Patrick Hund wrote, but attaching the catch statements to the promises being awaited, rather than wrapping in try/catch blocks, and extracting the error logic into a single function.
async save() {
let tenant = this.modelFor(this.routeName).tenant;
let app = this.modelFor(this.routeName).app;
await tenant.save().catch(handleError.bind(this, false));
await tenant.get('app').save({ adapterOptions: { tenantId: tenant.id }})
.catch(handleError.bind(this, true));
this.transitionTo('tenants.tenant.info', tenant.id);
// isApp is bound via bind; error will be provided when called via catch
function handleError(isApp, error) {
if (isApp) {
tenant.get('app').rollback();
} else {
tenant.rollback();
}
throw error;
}
}
I am trying to use await-async without try-catch for this:
const getUsers = async (reject, time) => (
new Promise((resolve, reject) => {
setTimeout(() => {
if (reject) {
reject(....)
}
resolve(.....);
}, time);
})
);
module.exports = {
getUsers ,
};
With try-catch block it looks like this:
const { getUsers } = require('./users');
const users = async () => {
try {
const value = await getUsers(1000, false);
.....
} catch (error) {
.....
}
}
users();
How can I write the same code without using the try-catch block?
Using the promise functions then-catch to make the process simpler I use this utils :
// utils.js
const utils = promise => (
promise
.then(data => ({ data, error: null }))
.catch(error => ({ error, data: null }))
);
module.exports = utils;
And then
const { getUsers } = require('./api');
const utils = require('./utils');
const users = async () => {
const { error, data } = await utils(getUsers(2000, false));
if (!error) {
console.info(data);
return;
}
console.error(error);
}
users();
Without using the try-catch block I got the same output, this way makes it better to understand the code.
In Extension to L Y E S - C H I O U K H's Answer:
The Utils Function is actually correct but, make sure to add the return keyword before the promise as shown down below:
// utils.js
const utils = promise => (
return promise
.then(data => { [data, null]; })
.catch(error => { [null, error]; });
);
module.exports = utils;
When Calling in Main Code:
let resonse, error; // any variable name is fine make sure there is one for error and the response
[response, error] = await utils(any_function()); // Make sure that inside the tuple, response is first and error is last like: [response, error].
if (error) console.log(error);
// -- Do Whatever with the Response -- //
Using My Method Would Give you Benefits like:
Your Own Variable Names.
Not Running into Type Safety issues when using Typescript.
Good Reason to Strong Type your code.
Personally, I have been using this in my code lately, and has reduced some many headaches, my code is cleaner, I don't have to stick with the same variable names, especially when working on a large codebase.
Happy Coding :)
See Ya!
If you have a valid default for the error case you can use the catch method on the getUsers promise and then await a promise whose error will be handled
const users = async () => {
const value = await getUsers(1000, false).catch(e => null);
}
While this approach should work it should be noted that this may mask the case when getUsers returns null vs when it raises an error, and you will still need to check for the null or get a null access error. All in all I would stick with the try { .. } catch (e) { ... } for most casses
A package I found called await-to-js can also help it.
import to from 'await-to-js';
const [err, users] = await to(getUsers());
if(err) doSomething();
The idea is like Lyes CHIOUKH's method, just a wrapper. Copied the source code here.
/**
* #param { Promise } promise
* #param { Object= } errorExt - Additional Information you can pass to the err object
* #return { Promise }
*/
export function to<T, U = Error> (
promise: Promise<T>,
errorExt?: object
): Promise<[U | null, T | undefined]> {
return promise
.then<[null, T]>((data: T) => [null, data])
.catch<[U, undefined]>((err: U) => {
if (errorExt) {
Object.assign(err, errorExt);
}
return [err, undefined];
});
}
export default to;
If you have such above single line async/await function, then this would have been clean code for you:
const getUsers = async (time, shouldReject=false) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (shouldReject) {
reject(Error('Rejected...'));
} else {
resolve(["User1", "User2"]);
}
}, time);
});
}
const userOperation = users => {
console.log("Operating user", users);
}
// Example 1, pass
getUsers(100)
.then(users => userOperation(users))
.catch(e => console.log(e.message));
// Example 2, rejected
getUsers(100, true)
.then(users => userOperation(users))
.catch(e => console.log(e.message));
And for multiple await in a single async function, it would good to have try/catch block as below:
const users = async () => {
try {
const value = await getUsers(1000, false);
const value1 = await getUsers2(1000, false);
...
} catch (error) {
...
}
}