Use async / await with fetch and foreach - javascript

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

Related

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
}

Multiple try-catch blocks inside async function

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.

How can I manage to make diffrent request after first request with failed status

I try to fetch some object, but the problem is that I need to check first if there ist any object on cache endpoint, if not I should do normal fetching from regular endpoint.
So far I only managed to do fetching from:
Normal endpoint and set everything on state,
Cache endpoint and set everything on state
Any attempts to mix both methods ended in failure :(
How can I mix this two methods?
const getCache = async () => {
try {
const apiCall = await fetch(fetchCacheEndpoint)
const data = await apiCall.json()
return data
} catch (e) {
console.log(e);
}
}
const pageOne = getCache().then((result) => {
const convertedOBj = result.doSomeSeriousStuff()
this.setState({
desiredObj: convertedOBj
})
})
I expected to do something like this
const getCache = async () => {
try {
const apiCall = await fetch(fetchCacheEndpoint)
const data = await apiCall.json()
return data
} catch (e) {
console.log(e);
}
}
let convertedOBj
const pageOne = getCache().then((result) => {
if ((result === undefined) || (!result) || (result && !result.length > 0)) {
const makeRegularFetch = async () => {
const regularFetch = await fetch(regularEndPoint)
const data = await regularFetch.json()
}
const pageTwo = makeRegularFetch ().then((result) => {
convertedOBj = result.doSomeSeriousStuff()
this.setState({
desiredObj: convertedOBj
})
})
} else {
convertedOBj = result.doSomeSeriousStuff()
this.setState({
desiredObj: convertedOBj
})
}
})
After first fetch (getCache) is failed there is another fetch (makeRegularFetch) to second endpoint which is always fine, but only in the case when first(getCache) return empty object or just any kind of error
How can I handle this kind of action?
From what I can see in your second part of your code, you never execute your pageOne function.
Try pageOne() after your definition.
However I made a fiddle on stackblitz for your case: https://stackblitz.com/edit/js-eufm8h
If you don't understand something, feel free to ask.

Variable Retains Original Value Despite Having New Value Earlier and Further within a Function

let getProjects = function() {
try {
return axios.get('https://app.asana.com/api/1.0/projects/')
} catch (error) {
console.error(error)
}
}
let getTasks = function(project) {
try {
return axios.get('https://app.asana.com/api/1.0/projects/'+project+'/tasks')
} catch (error) {
console.error(error)
}
}
async function getAsanaData() {
let projects = await getProjects()
projects = projects.data.data
projects.map(async (project) => {
//project.attachments = []
let tasks = await getTasks(project.gid)
if(tasks != undefined){
tasks = tasks.data.data
project.tasks = tasks
//console.log(projects)
}
})
console.log(projects)
return projects
}
Promise.try(() => {
return getAsanaData();
}).then((result) => {
//console.log(util.inspect(result, {showHidden: false, depth: null}))
//var asanaData = safeJsonStringify(result);
//fs.writeFile("thing.json", asanaData);
})
.catch(err=>console.log(err))
In getAsanaData(), projects has a new value after project.tasks = tasks.
However, it's original value is printed by console.log(projects) before return projects.
This of course also means that the original value rather than the necessary new value will be returned.
What is the cause and how do I resolve this?
Try this:
async function getAsanaData() {
let projects = await getProjects()
return Promise.all(projects.data.data.map(async (project) => {
let tasks = await getTasks(project.gid)
project.tasks = !!tasks ? tasks.data.data : project.tasks
return project
}))
}
This will go through the async calls to getTasks and return for each the project. Then you should get them all resolved via Promise.all

Angular 6 : async await some variable got it variable

Under my Angular 6 app, I have a variable "permittedPefs" which is getting value after an HTTP call (asynchronous)
#Injectable()
export class FeaturesLoadPermissionsService {
permittedPefs = [];
constructor() {
this.loadUserPefsService.getUserRolePefs(roleId)
.subscribe(
(returnedListPefs) => {
this.permittedPefs = returnedListPefs;
},
error => {
console.log(error);
});
}
}
in another method, I'm using that same variable:permittedPefs
But as it's initially empty and it gots its value after such a time, so I need to wait for it to re-use it.
I've tried to use async-await, and my purpose is waiting for permittedPefs to got aobject value
async checkPefPresence(pefId) {
const listPefs = await this.permittedPefs
}
how to fix it ??
As loadUserPefsService.getUserRolePefs method returns Observable you can store it and subscribe to it later when you need it.
#Injectable()
export class FeaturesLoadPermissionsService {
permittedPefs = [];
constructor() {
this.userRolePefsObservable = this.loadUserPefsService.getUserRolePefs(roleId);
}
}
checkPefPresence(pefId) {
let listPefs;
this.userRolePefsObservable.subscribe(
(returnedListPefs) => {
listPefs = returnedListPefs;
},
error => {
console.log(error);
});
}
Use a behaviorSubject
#Injectable()
export class FeaturesLoadPermissionsService {
permittedPefs: BehaviorSubject<any[]> = new BehaviorSubject([]);
constructor() {
this.loadUserPefsService.getUserRolePefs(roleId)
.subscribe((returnedListPefs) => {
this.permittedPefs.next(returnedListPefs);
},
error => {
console.log(error);
});
}
}
Then where ever you are checking for it(remember to unsubscribe when you are done)
if it has to be async you can do it like below
async checkPefPresence(pefId): Promise {
return new Promise((resolve, reject) => {
this.featuresLoadPermissionsService.permittedPefs.subscribe(listPefs => {
//handle whatever check you want to do here
resolve();
},reject);
})

Categories

Resources