Multiple try-catch blocks inside async function - javascript

I have an async function where there is some operations like this:
async function myAsyncFunction(argument) {
let arrayTasks = [
{
const run = async () => {
let response = await getDataAsync();
}
},
{
const run = async () => {
let response = await getDataAsync();
}
}
]
for (const index in arrayTasks) {
await arrayTasks[index].run();
}
}
The function above is a simplified version of my code, it works. But I am not sure where do I need to put try-catch block:
Wrapping all content function:
async function myAsyncFunction(argument) {
try{
// All code
}catch (e) {
// catch code
}
}
Or inside my asyncs functions and for operator:
async function myAsyncFunction(argument) {
let arrayTasks = [
{
const run = async () => {
try{
let response = await getDataAsync();
}catch (e) {
// catch code
}
}
},
{
const run = async () => {
try{
let response = await getDataAsync();
}catch (e) {
// catch code
}
}
}
]
for (const index in arrayTasks) {
try{
await arrayTasks[index].run();
}catch (e) {
// catch code
}
}
}
What is a correct way?
The arrayTasks variable is dynamic length on my original code.

Depends how and where you want to handle failure.
One approach to "an array of asynchronous tasks whose execution may fail" is a pattern like this:
async function myAsyncFunction(argument) {
const success = async res => ({ success: await res }) // Promise<{success: res}>
const error = async err => ({ error: await err }) // Promise<{error: e}>
const arrayTasks = [
{
run: async () => getDataAsync()
}
},
{
run: async () => getDataAsync()
}
]
const runWithResult = task => task.run().then(success).catch(error)
const outcomes = await Promise.all(arrayTasks.map(runWithResult))
console.log(outcomes) // array of {error: e} | {success: res}
}
You can chain a .catch() handler on an async function, and it has the same effect as wrapping it in try/catch.
More here, if you are interested. The section "Refactor without fp-ts" shows how to reduce that array from [{error: e} | {success: res}] to {error: [], success: []}, which is way easier to work with:
const { error, success } = outcomes.reduce((acc, o) => o.error ?
{ error: [...acc.error, o.error], success: acc.success } :
{ error: acc.error, success: [...acc.success, o.success] },
{ error: [], success: [] })
This is an FP type called Either - an operation may return "either" (in this case) a value or an error.
Your code doesn't throw with this approach.
If you know that something may fail, it's not exceptional. Exceptions are when some unexpected failure occurs, IMHO.
"Tasks that may fail" that are known ahead of time just need the error path code written.
If you take this approach, I recommend building it as a first-class state reducing machine, like this:
// Takes an array of async tasks that may throw of shape {run: () => Promise<result>}
// Returns Promise<{error: error[], success: result[]}>
async function executeAsyncTasks(arrayOfAsyncTasks) {
const success = async res => ({ success: await res }) // Promise<{success: res}>
const error = async err => ({ error: await err }) // Promise<{error: e}>
const runWithResult = task => task.run().then(success).catch(error)
const outcomes = await Promise.all(arrayOfAsyncTasks.map(runWithResult))
const outcomesFlat = outcomes.reduce((acc, o) => o.error ?
{ error: [...acc.error, o.error], success: acc.success } :
{ error: acc.error, success: [...acc.success, o.success] },
{ error: [], success: [] })
return outcomesFlat
}

It all depends... My rule of thumb is only catch something if you intend on doing something with it, i.e some error handling. If the handling of the error is going to be the same across all cases, wrap the it all with one try/catch, else wrap individual cases, with their own error handling code.

Related

How to refactor an async function with Promise inside of it

Having this async function that returns a Promise:
async function doSomething(userId) {
return new Promise(async (resolve, reject) => {
const query = 'my-query';
const roomAggregate = Room.aggregate(query).allowDiskUse(true);
Room.aggregatePaginate(roomAggregate, async function (err, data) {
if (err || !data) {
throw err;
return {
success: false,
rooms: [],
};
} else {
const rooms = [];
for (let room of data.docs) {
const roomId = room._id;
room = await computeAThing(room, {
loadWriteUps: false,
loadCreators: false,
});
room.userCompleted = await computeBThing(userId, roomId);
rooms.push(room);
}
return resolve({
rooms,
success: true,
paginator: {
// some data related to pagination
},
});
}
});
});
}
I'm not sure if it really needs to contain new Promise inside as it is already declared as async function. Is it mandatory in this case?
Because when that part was removed and at the end instead of return resolve({...}) it is only return {...} it seems to not settle.
Here is the modified code with new Promise and different return:
async function doSomething(userId) {
const query = 'my-query';
const roomAggregate = Room.aggregate(query).allowDiskUse(true);
Room.aggregatePaginate(roomAggregate, async function (err, data) {
if (err || !data) {
throw err;
return {
success: false,
rooms: [],
};
} else {
const rooms = [];
for (let room of data.docs) {
const roomId = room._id;
room = await computeAThing(room, {
loadWriteUps: false,
loadCreators: false,
});
room.userCompleted = await computeBThing(userId, roomId);
rooms.push(room);
}
return {
rooms,
success: true,
paginator: {
// some data related to pagination
},
};
}
});
}
This method is used somewhere else in this way:
const myInfo = await someObj.doSomething('myUserId');
and then checked:
if (myInfo.success) { ... }
For the first way of writing the function it works fine, for the second one myInfo is undefined and it throws and error that cannot read success of undefined.
Is something missing from the implementation?
For the second version I can see that you actually not returning anything from doSomething so I think you should do the below:
async function doSomething(userId) {
const query = 'my-query';
const roomAggregate = Room.aggregate(query).allowDiskUse(true);
const obj = Room.aggregatePaginate(roomAggregate, async function (err, data) {
if (err || !data) {
throw err;
return {
success: false,
rooms: [],
};
} else {
const rooms = [];
for (let room of data.docs) {
const roomId = room._id;
room = await computeAThing(room, {
loadWriteUps: false,
loadCreators: false,
});
room.userCompleted = await computeBThing(userId, roomId);
rooms.push(room);
}
return {
rooms,
success: true,
paginator: {
// some data related to pagination
},
};
}
});
return obj;
}
In general, you don't need to explicitly return a Promise in an async function as by default the returned value will be wrapped in a Promise , whatever it is. You just need to return something
Room.aggregatePaginat is written in coninuation-passing style, which does not interface well with promises. A generic promisify function can be used to convert any continuation-passing style function into a promise-based one. If you don't wish to write this function yourself, it is provided by Node as util.promisify -
const promisify = f => (...args) =>
new Promise((resolve, reject) =>
f(...args, (err, data) => err ? reject(err) : resolve(data))
)
Now it's easy to refactor your doSomething. Note use of Promise.all means all rooms data is processed in parallel rather than in series -
async function doSomething(userId) {
const query = 'my-query'
const roomAggregate = Room.aggregate(query).allowDiskUse(true)
const {docs:rooms} = await promisify(Room.aggregatePaginate)(roomAggregate)
return {
rooms:
await Promise.all(rooms.map(async room => ({
...await computeAThing(room, {loadWriteUps: false, loadCreators: false}),
userCompleted: await computeBThing(userId, room._id)
}))),
success: true,
paginator: ...
}
}
Also you should avoid things like { success: true } because a resolved promise is inherently "successful". A rejected one is not.
And watch out for return occurring after a throw. In this case the return is unreachable so it doesn't do what you think it's doing.
if (err || !data) {
throw err;
return { // unreachable
success: false, // these four lines
rooms: [], // are completely ignored
}; // by javascript runtime
}
Again, { success: false } goes against the Promise pattern anyway. If you want to recover from a rejection and recover with an empty list of { rooms: [] }, do it like this instead -
doSomething(user.id)
.catch(err => ({ rooms: [] }))
.then(res => console.log(res.rooms))
Better yet, you can try..catch inside doSomething and return the appropriate empty response in the event of an error. This prevents the error from bubbling up and forcing the user to handle it -
async function doSomething(userId) {
try { // try
const query = 'my-query'
const roomAggregate = Room.aggregate(query).allowDiskUse(true)
const {docs:rooms} = await promisify(Room.aggregatePaginate)(roomAggregate)
return {
rooms:
await Promise.all(rooms.map(async room => ({
...await computeAThing(room, {loadWriteUps: false, loadCreators: false}),
userCompleted: await computeBThing(userId, room._id)
}))),
paginator: ...
}
}
catch (err) { // catch
return { rooms: [] }
}
}
doSomething(user.id)
.then(res => console.log(res.rooms))

async await not working in composable function vue 3

In my project I have a function for downloading the files. When click the button the function onDownload will be called:
import {useOnDownload} from "../../use/useOnDownload"
setup() {
...
const loading = ref(null)
onDownload = (id) => {
loading.value = id
await useOnDownload(id)
loading.value = null
}
return {loading, onDownload}
}
I refactored the code for api in a file useOnDownload.js call because the same code is used in another components as well.
export async function useOnDownload(id) {
// make api call to server with axios
}
What I did wrong? I need to wait for the function useOnDownload ... in order the loader to work.
Here is how to make async composable functions with async await syntax
export default function useOnDownload() {
const isLoading = ref(true);
const onDownload = async () => {
isLoading.value = true;
try {
const { data } = await axios.post('/api/download', {id: id},
{responseType: 'blob'})
// handle the response
} catch (error) {
console.log(error);
} finally {
isLoading.value = false;
}
};
// invoke the function
onDownload();
return { // return your reactive data here };
}
import useOnDownload from "../../use/useOnDownload"
// no await in setup script or function
const { reactiveDataReturned } = useOnDownload();
Read more here
onDownload must be async in order to use await within it
I managed to solved another way without async and await...
I passed the reference object loader to the function parameter (as optional) and handle from there...
export function useOnDownload(id, loader) {
if(loader !== undefined) {
loader.value = id
}
axios.post('/api/download', {id: id}, {
responseType: 'blob'
}).then(response => {
// handle the response
...
if(loader !== undefined) {
loader.value = null
}
}).catch(error => {
// handle the error
...
if(loader !== undefined) {
loader.value = null
}
})
}
You are using the await keyword in your onDownlaod function, but the function is not asynchronous. Here's how you should update it.
// next line important
onDownload = async(id) => {
loading.value = id
await useOnDownload(id)
loading.value = null
}

Use async / await with fetch and foreach

This code should do the following:
Search all links on the current page and check for multiple errors.
The check should be before showing up the results on the page.
So I want to fill an array with errors and return it after all checks are finished.
interface LinkObject {
element: HTMLAnchorElement;
url: URL;
checkTrigger?: HTMLButtonElement;
}
interface ErrorObject {
element: HTMLElement;
errors: string[];
warnings: string[];
}
export default class LinkCheckTool {
protected linksObjects: LinkObject[] = [];
protected errors: ErrorObject[] = [];
constructor() {
document.querySelectorAll('a').forEach((linkElement) => {
const button: HTMLButtonElement = document.createElement('button');
button.classList.add('tb-o-linkobject__btn');
const url: URL = new URL(linkElement.href);
if (url) {
linkElement.appendChild(button);
this.linksObjects.push({
element: linkElement,
url: url,
checkTrigger: button
})
}
})
const errors = this.fetchErrors();
console.log(errors); // results in an empty array, so need to use async / await here
}
protected fetchErrors() {
const errors = [];
this.linksObjects.forEach((linkObject) => {
if (linkObject.url.protocol !== 'javascript:') {
fetch(linkObject.url.href)
.then((response) => (response))
.then((response) => {
if (!response.ok) {
// push to errors here
}
})
.catch((error) => {
// push to errors here
})
}
})
}
}
In this case, the console output of errors returns an empty array, of course. How can I use async / await and return a promise here?
Function fetchErrors is not async here, because it does not return Promise. And since you try to call the function in the constructor async/await syntax won't really work in this context.
What you need to do instead is to use Promise callback here. You can apply the Promise.allSettled method. It will help you to wait until your requests will get resolved and then you can handle the responses one by one.
constructor() {
// ...
const errors = [];
this.fetchErrors().then(results => {
results.forEach(result => {
if (result.status === "rejected") {
errors.push(result.value)
}
})
console.log(errors); // will print you list of errors
});
}
protected fetchErrors() {
const requests = this.linksObjects
.filter(linkObject => linkObject.url.protocol !== 'javascript:')
.map((linkObject) => fetch(linkObject.url.href))
return Promise.allSettled(requests);
}

Restart then() block when catch is returned an error

I want to restart my code when catch function is returned an error.
let ig = require('instagram-scraping')
module.exports.instagram = async (req, res) => {
ig.scrapeTag('postthisonmypersonalblogtoo').then(async result => {
let myPostCodes = [];
result.medias.forEach(content => {
if(content.owner_id == '10601516006'){
myPostCodes.push(content.shortcode);
}
})
await res.render('instagram', {
myPosts : myPostCodes
});
}).catch((err) => {
console.error(err);
// if(err){
//ig.scrapeTag('postthisonmypersonalblogtoo').then(async result => { ... } //do same things as above
}
})
}
The reason that i wanted to do this: sometimes ig.scrapeTag method find my posts but sometimes cant find anything and return me that;
Error: Error scraping tag page "postthisonmypersonalblogtoo"
at Request._callback (C:\Users\Byte\Desktop\BLOG\node_modules\instagram-scraping\index.js:114:24)
at Request.self.callback (C:\Users\Byte\Desktop\BLOG\node_modules\request\request.js:185:22)
So when i take this error i want to reset ig.scrapeTag to research my posts on instagram again
(By the way sorry for my bad English and if you guys have a advice for another instagram api let me know (api can be official or unofficial doesn't matter)
I would move scrape functionality into separate function and introduce retry counter to keep track of number of retries. Also not sure why you mix and match then/catch with async/await. I think it is more readable and consistent to use async/await everywhere. Something like this:
let ig = require('instagram-scraping')
const MAX_RETRY_COUNT = 2;
async function scrapeInstagram() {
let retryCount = 0;
const scrapeTag = async () => {
try {
const result = await ig.scrapeTag('postthisonmypersonalblogtoo');
return result;
}
catch(e) {
if (retryCount < MAX_RETRY_COUNT) {
retryCount++;
scrapeTag();
} else {
throw e;
}
}
}
const result = await scrapeTag();
let myPostCodes = [];
result.medias.forEach(content => {
if(content.owner_id == '10601516006'){
myPostCodes.push(content.shortcode);
}
});
return myPostCodes;
}
module.exports.instagram = async (req, res) => {
try {
const myPostCodes = await scrapeInstagram();
await res.render('instagram', {
myPosts : myPostCodes
});
}
catch(e) {
console.log(e);
res.status(500).send("Could not load from Instagram")
}
}

How to chain Promises while keeping the data?

I have two API-calls chained like so:
let weatherPromise;
if (crds) {
weatherPromise = mapbox.getLocation(crds)
.then(locData => darksky.getWeather(crds))
.then(weatherData => Object.assign(weatherData, {city: locData.city}));
} else if (term) {
weatherPromise = mapbox.getCoords(term)
.then(locData => darksky.getWeather(locData.crds));
} else {
weatherPromise = Promise.reject({error: 'Aborted due to unexpected POST-Body'});
}
weatherPromise.then(weatherData => {
io.emit('update', weatherData);
console.log(`Answer sent: ${JSON.stringify(weatherData)}`);
}, reject => {
io.emit('update', reject);
console.error(reject.error);
});
But it is not working at all. The error-handling is all over the place (mainly just logging undefined and failing) and for example locData doesn't seem to be accessible in that second then() anymore. I tried nesting Promises and it worked fine but I would like to avoid that anti-pattern if I can.
You can nest `.thensĀ“, but in this case, I'd clearly go with async / await:
async function retrieveWeather() {
if (crds) {
const locData = await mapbox.getLocation(crds);
const weatherData = await darksky.getWeather(crds);
return Object.assign(weatherData, {city: locData.city});
} else if (term) {
const locData = await mapbox.getCoords(term);
const weatherData = await darksky.getWeather(locData.crds);
return weatherData;
} else {
throw {error: 'Aborted due to unexpected POST-Body'};
}
}
(async function sendWeather() {
try {
const weatherData = await retrieveWeather();
io.emit('update', weatherData);
console.log(`Answer sent: ${JSON.stringify(weatherData)}`);
} catch(error) {
io.emit('update', error);
console.error(reject.error);
}
})();

Categories

Resources