Convert Ember function to use ES2017 async/await - javascript

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;
}
}

Related

Try/catch async function outside of async context

I have a few classes which use 'dns' from node.js. But when an error occurs, my app is thrown. I made a siimple example with classes and throwable functions and I faced with the same problem. It's works if an exception is thrown from function but it doesn't work if an exception is thorwn from class.
Example:
class Test {
constructor() {
this.t();
}
async t() {
throw new Error("From class");
}
}
async function test(){
new Test();
}
try {
test().catch(e => {
console.log("From async catch");
});
} catch (e) {
console.log("From try catch");
}
Output:
Uncaught (in promise) Error: From class
at Test.t (<anonymous>:6:11)
at new Test (<anonymous>:3:10)
at test (<anonymous>:11:3)
at <anonymous>:15:3
How to catch errors from try/catch block in this example?
UPD:
Full code (typescript):
export class RedisService {
client: any;
expirationTime: any;
constructor(args: RedisServiceOptions) {
let redisData: any = {};
if (args.password)
redisData["defaults"] = { password: args.password };
dns.resolveSrv(args.host, (err, addresses) => {
if (err) {
/// Handling error in main func
}
else {
log.info("Using Redis cluster mode");
redisData["rootNodes"] = addresses.map(address => {
log.info(`Adding Redis cluster node: ${address.name}:${address.port}`);
return Object({ url: `redis://${address.name}:${address.port}` })
});
this.client = createCluster(redisData);
};
this.client.on('error', (err: Error) => log.error(`Redis error: ${err.message}`));
this.client.connect().then(() => { log.info("Connected to Redis") });
});
this.expirationTime = args.expirationTime;
}
/// Class functions
}
it doesn't work if an exception is thrown from class.
In particular, when an asynchronous error event occurs in the constructor, yes. Like your question title says, you can't handle errors outside of an async context, and a constructor is not that.
Your current implementation has many issues, from client being undefined until it is initialised to not being able to notify your caller about errors.
All this can be solved by not putting asynchronous initialisation code inside a constructor. Create the instance only once you have all the parts, use an async helper factory function to get (and wait for) the parts.
export class RedisService {
client: RedisClient;
expirationTime: number | null;
constructor(client: RedisClient, expirationTime: number | null) {
this.client = client;
this.expirationTime = expirationTime;
}
static create(args: RedisServiceOptions) {
const addresses = await dns.promises.resolveSrv(args.host);
log.info("Using Redis cluster mode");
const redisData = {
defaults: args.password ? { password: args.password } : undefined,
rootNodes: addresses.map(address => {
log.info(`Adding Redis cluster node: ${address.name}:${address.port}`);
return { url: `redis://${address.name}:${address.port}` };
}),
};
const client = createCluster(redisData);
client.on('error', (err: Error) => log.error(`Redis error: ${err.message}`));
await this.client.connect();
log.info("Connected to Redis");
return new RedisService(client, args.expirationTime);
}
… // instance methods
}
Now in your main function, you can call create, use await, and handle errors from it:
async function main(){
try {
const service = await RedisService.create(…);
} catch(e) {
console.log("From async catch", e);
}
}
You generate multiple async-requests but you can only catch errors from the first one:
You create a promise with async function test().
Then you create a syncronous call within it, with new Test(), every error syncronously thrown from within it will be catched by the catch.
Then you generate another promise call from within the syncronous constructor, this error can't be caught by the try/catch-block or .catch at, or above the async function test().
It is similar to this:
constructor() {
new Promise(() => throw new Error(''))
}
So you have 3 possible ways to solve it:
You catch the error inside the async t() {}-call.
You catch it inside the constructor with this.t().catch(console.error) (which can't be forwarded to the try/catch block) as it is forwarded to the catch block of the Promise behind the async call. And if there is no .catch on the async call, you get the "Unhandled Promise rejection"-Error.
Don't call the async function from within the constructor at all, use it like this:
class Test {
async t() {
throw new Error("From class");
}
}
async function test(){
await (new Test()).t();
}
try {
test().catch(e => {
console.log("From async catch");
});
} catch (e) {
console.log("From try catch");
}
Don't make async methods)))
Your solution looks somewhat like this.
class Test {
constructor() {
this.t();
}
t() {
(async () => {
try {
throw new Error("From class");
} catch (e) {
console.log(e);
}
})();
}
}
Have a nice rest of your day

Typescript: Variable is used before being assigned in strict mode

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;
}

"await is only valid in async function" in for loop

I'm being told that "await is only valid in async function", even though it is in a async function. Here is my code:
async function uploadMultipleFiles (storageFilePaths,packFilePaths,packRoot) {
return new Promise((resolve,reject) => {
try {
for (i in storageFilePaths) {
await uploadFile(storageFilePaths[i],packFilePaths[i],packRoot) // error throws on this line
}
resolve("files uploaded")
} catch {
console.log(err)
reject("fail")
}
})
}
Why is this happening when I made it an async function? Is it because I am using a for loop? If so, how can I get the expected outcome without this error?
The function you define starting on line 1 is async.
The arrow function you define on line 2 and pass to the Promise constructor is not async.
You are also using the multiple promise anti-pattern. Get rid of the Promise constructor entirely. Just return the value when you have it. That's one of the main benefits of the async keyword.
async function uploadMultipleFiles(storageFilePaths, packFilePaths, packRoot) {
try {
for (i in storageFilePaths) {
await uploadFile(storageFilePaths[i], packFilePaths[i], packRoot) // error throws on this line
}
return "files uploaded";
} catch {
console.log(err);
throw "fail";
}
}
You can only use await inside of an async function, the error refers to the callback your passing to your new Promise (since you are entering a new function scope there).
async function uploadMultipleFiles (storageFilePaths,packFilePaths,packRoot) {
return new Promise((resolve,reject) => { // <========= this arrow function is not async
try { // so you cant use await inside
for (i in storageFilePaths) {
await uploadFile(storageFilePaths[i],packFilePaths[i],packRoot) // error throws on this line
}
resolve("files uploaded")
} catch {
console.log(err)
reject("fail")
}
})
}
The part where you try to construct a new Promise is actually redundant since an async function will resolve to a Promise anyways (read more here). So you could write your code as follows:
async function uploadMultipleFiles (storageFilePaths,packFilePaths,packRoot) {
try {
for (i in storageFilePaths) {
await uploadFile(storageFilePaths[i],packFilePaths[i],packRoot) // error throws on this line
}
return "files uploaded"
} catch {
console.log(err)
throw new Error("fail");
}
}
The Promise callback isn't async
async function uploadMultipleFiles (storageFilePaths,packFilePaths,packRoot) {
return new Promise(async (resolve,reject) => {
try {
for (i in storageFilePaths) {
await uploadFile(storageFilePaths[i],packFilePaths[i],packRoot) // error throws on this line
}
resolve("files uploaded")
} catch {
console.log(err)
reject("fail")
}
})
}

How to properly implement mongodb async/await inside a promise?

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

Using async/await and try/catch to make tiered api calls

SO friends!
I need to make a call to an api; if it fails, I need to make a call to same api with different param; if it fails AGAIN, I need to make a call to same api with a third, different param; if it fails finally after that, is an actual error and can bugger out.
Only way I can figure to do this is with nested try/catch statements, ala:
const identityCheck = async (slug) => {
let res;
try {
res = await Bundle.sdk.find(slug);
} catch (err) {
console.log('Fragment didn\'t work ========', slug, err);
try {
res = await Bundle.sdk.find(`package/${slug}`);
} catch (e) {
console.log('Fragment didn\'t work package ========', e);
try {
res = await Bundle.sdk.find(`${slug}-list`);
} catch (error) {
console.log('None of the fragments worked================.', error);
}
}
}
return logResponse(res);
};
identityCheck('fashion');
But seems like there MUST be another simpler way to do this. I tried boiling down into a retry function, but that just ends up being even more code and way less clear:
const identityCheck = (slug) => {
const toTry = [
slug,
`package/${slug}`,
`${slug}-list`
];
return new Promise((resolve, reject) => {
let res;
let tryValIndex = 0;
const attempt = async () => {
try {
res = await Bundle.sdk.find(toTry[tryValIndex]);
return resolve(logResponse(res));
} catch (err) {
console.log(`toTry ${toTry[tryValIndex]} did not work ========`, slug, err);
if (tryValIndex >= toTry.length) {
return reject(new Error('Everything is broken forever.'));
}
tryValIndex++;
attempt();
}
};
attempt();
});
};
Guidance and opinions appreciated!
Avoid the Promise constructor antipattern, and use a parameter instead of the outer-scope variable for the recursion count:
function identityCheck(slug) {
const toTry = [
slug,
`package/${slug}`,
`${slug}-list`
];
async function attempt(tryIndex) {
try {
return await Bundle.sdk.find(toTry[tryIndex]);
} catch (err) {
console.log(`toTry ${toTry[tryIndex]} did not work ========`, slug, err);
if (tryIndex >= toTry.length) {
throw new Error('Everything is broken forever.'));
} else {
return attempt(tryIndex+1);
}
}
}
return attempt(0);
}
Following on Bergi's answer, but trying to preserve the original structure to avoid "even more code":
const idCheck = async (slug, alsoTry = [`package/${slug}`, `${slug}-list`]) => {
let res;
try {
res = await Bundle.sdk.find(slug);
} catch (err) {
if (!alsoTry.length) throw err;
return idCheck(alsoTry.shift(), alsoTry);
}
return logResponse(res);
};
idCheck('fashion');
This takes advantage of default arguments being quite powerful.
Same complexity, but aesthetically closer to the nested try-blocks, a simpler pattern perhaps.

Categories

Resources