Array of strings getting converted to Objects - javascript

I'm pushing files to amazon using pre-signed URLs, and modifying the files array with the file name reference inside the newData object. (The files array are inside an array of objects called items)
// Add job
const addJob = async(data, user) => {
const newData = { ...data };
data.items.map((item, itemIndex) => {
if (item.files !== []) {
item.files.map(async(file, fileIndex) => {
const uploadConfig = await axios.get(`/api/s3upload`, {
params: {
name: file.name,
},
});
console.log(uploadConfig.data.key);
newData.items[itemIndex].files[fileIndex] = uploadConfig.data.key;
await axios.put(uploadConfig.data.url, file);
});
}
});
console.log(newData);
try {
const res = await axios.post('/api/jobs', newData);
dispatch({
type: ADD_JOB,
payload: res.data,
});
} catch (error) {
console.log(error);
}
};
The file references comes in the uploadConfig.data.key and are being save into the newData object.
When this function is executed, something peculiar happens:
the console log of newData returns the correct array of references to the files
the files are uploaded just fine
the request made to /api/jobs, which is passing newData, sends an array of objects that contains { path: ... }
console.log(newData):
Post request:

JavaScript does this because forEach and map are not promise-aware. It cannot support async and await. You cannot use await in forEach or map.
for loops are promise-aware, thus replacing the loops with for loops and marking them as await returns the expected behaviour.
source: zellwk article
Corrected (functioning) code:
const addJob = async (data, user) => {
const newData = { ...data };
const { items } = data;
const loop = async () => {
for (let outer in items) {
if (items[outer].files !== []) {
const loop2 = async () => {
for (let inner in items[outer].files) {
const uploadConfig = await axios.get(`/api/s3upload`, {
params: {
name: items[outer].files[inner].name,
},
});
const res = await axios.put(uploadConfig.data.url, items[outer].files[inner])
newData.items[outer].files[inner] = uploadConfig.data.key;
}
};
await loop2();
}
}
};
await loop();
try {
const res = await axios.post('/api/jobs', newData);
dispatch({
type: ADD_JOB,
payload: res.data,
});
} catch (error) {
console.log(error);
}
};

Related

Callback with recursive functions

I am using the google translate api to translate data from a json file to french locale and then write it back to a file. I am using a recursive function to iterate over the json file since it is deeply nested. However the execution is not waiting till the translation is completed before it writes to the file. I have tried using callback and promise approaches but i couldn't get it right.
Just for it to work as I required an output as an emergency I have set a timout before the write method is called. It work but I would like to learn the appropriate/correct approach to implement this.
const fs = require('fs')
const {Translate} = require('#google-cloud/translate').v2
require('dotenv').config()
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0
const credentials = JSON.parse(process.env.credentials)
const translate = new Translate({
credentials,
projectId: credentials.project_id,
})
let data = {}
// writeJSONTofile should be executed only after readJSONFile execution is completed
//read file
const readJSONFile = () => {
try {
data = JSON.parse(fs.readFileSync('...\\locale\\en.json'))
iterateAndTranslate(data)
setTimeout(() => {
writeJSONToFile()
}, 25000)
} catch (error) {
console.log(error)
}
}
// iterate, translate, reassign
const iterateAndTranslate = async (data) => {
for(key in data) {
if (typeof data[key] === 'object' && data[key] !== null) {
iterateAndTranslate(data[key])
} else{
data[key] = await translateText(data[key], 'fr')
}
}
}
//translate method
const translateText = async (text, targetLanguage) => {
try {
let [response] = await translate.translate(text, targetLanguage)
return response
} catch (error) {
console.log(error)
return 0
}
}
const writeJSONToFile = async () => {
var outputFileName = 'C:\\test\\test.json'
await fs.writeFileSync(outputFileName, JSON.stringify(data,null,4), (err) => {
if(err) {
console.log(err)
} else {
console.log('Done!')
}
})
}
// start from here
readJSONFile()
You have a few issues with your code.
Your functions use a global variable and mutate it instead of getting input and returning output.
timeout will cause unexpected behavior in your case.
you are using var
you have redundant async-await on the writeJSONToFile function
See my view of point about the possible solution.
const fs = require("fs");
const { Translate } = require("#google-cloud/translate").v2;
require("dotenv").config();
process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0;
const credentials = JSON.parse(process.env.credentials);
const translate = new Translate({
credentials,
projectId: credentials.project_id,
});
// writeJSONTofile should be executed only after readJSONFile execution is completed
//read file
const readJSONFile = async () => {
try {
const data = JSON.parse(fs.readFileSync("...\\locale\\en.json"));
return iterateAndTranslate(data);
} catch (error) {
console.log(error);
}
return {};
};
// iterate, translate, reassign
const iterateAndTranslate = async (data) => {
for (let key in data) {
if (typeof data[key] === "object" && data[key] !== null) {
await iterateAndTranslate(data[key]);
} else {
data[key] = await translateText(data[key], "fr");
}
}
return data;
};
//translate method
const translateText = async (text, targetLanguage) => {
try {
let [response] = await translate.translate(text, targetLanguage);
return response;
} catch (error) {
console.log(error);
}
return null;
};
const writeJSONToFile = (data) => {
let outputFileName = "C:\\test\\test.json";
fs.writeFileSync(outputFileName, JSON.stringify(data, null, 4), (err) => {
if (err) {
console.log(err);
} else {
console.log("Done!");
}
});
};
// start from here
const run = async () => {
const data = await readJSONFile();
writeJSONToFile(data);
};
run();
See more:
why not using global varible
why not using var

Appending data to AsyncStorage

I am trying to append an Object to an existing array of objects in AsyncStorage, however not being successful. Any help is appreciated!
Current code:
const [storageItems, setStorageItems] = useState([]);
const handleHabitCreation = async () => {
setLoading(true);
const newHabit = {
name: name,
color: updatedColor,
days: daysCount,
times: timesCount,
reminder: selectedDate,
description: description,
};
try {
const jsonValue = await AsyncStorage.getItem('#habit');
setStorageItems(jsonValue);
} catch (error) {
console.error(error);
}
const stringifiedHabit = JSON.stringify(newHabit);
setStorageItems([...storageItems, stringifiedHabit]);
try {
await AsyncStorage.setItem('#habit', JSON.stringify(storageItems));
setTimeout(() => {
setLoading(false);
navigation.pop(3);
}, 2500);
} catch (error) {
console.error(error);
}
};
The proplem is that you are not parsing the stringified json data. You need to parse it to perform any modification. Save the data in the state as parsed json and srtingify it just before saving it to the async storage. Try this code:
try {
const jsonValue = await AsyncStorage.getItem('#habit');
// saving parsed json
const data = JSON.parse(jsonValue)
setStorageItems(data);
} catch (error) {
console.error(error);
}
// state depends on previous state
setStorageItems((prevState) => [...prevState, newHabit]);
or to make shorter
try {
const jsonValue = await AsyncStorage.getItem('#habit');
let data = JSON.parse(jsonValue) // parse to modify
// push newHabit as well
data.push(newHabit)
setStorageItems(data);
} catch (error) {
console.error(error);
}
in both approaches, stringify the object in the end
await AsyncStorage.setItem('#habit', JSON.stringify(storageItems));

How to use foreach and promise

I need to get datas with nested foreach, but I can't fill my array.
At the end of this code I would like to have an array (segId) with my datas but it is empty (because of aynschronous).
I read that I had to use Promise.all but I can't beacause my promise are nested
I'm beginner so my code is far from perfect
How can I do that ?
async function getActivities(strava, accessToken)
{
const payload = await strava.athlete.listActivities({'access_token':accessToken, 'after':'1595281514', 'per_page':'10'})
return payload;
}
async function getActivity(strava, accessToken, id)
{
const payload = await strava.activities.get({'access_token':accessToken, 'id':id, 'include_all_efforts':'true'})
return payload;
}
async function getSegment(strava, accessToken, id)
{
const payload = await strava.segments.get({'access_token':accessToken,'id':id})
return payload
}
var tableau = []
var segId = []
const activities = getActivities(strava, accessToken)
activities.then(value => {
value.forEach((element, index) => {
const activity = getActivity(strava, accessToken, element['id'])
activity.then(value => {
value['segment_efforts'].forEach((element, index) => {
const segment = getSegment(strava, accessToken, element['segment']['id'])
segment.then(value => {
segId.push(value['id'])
})
//console.log(segId)
});
});
})
}) console.log(segId)
Regards
PS : Sorry for my english ...
Something like this should work. You need to always return the inner promises to include them in your promise chain. Consider splitting the code into functions to make it more readable.
getActivities(strava, accessToken).then(activities => {
return Promise.all(activities.map(elem => {
return getActivity(strava, accessToken, elem['id']).then(activity => {
return Promise.all(activity['segment_efforts'].map(elem => {
return getSegment(strava, accessToken, elem['segment']['id']).then(segment => {
segId.push(segment['id']);
});
}));
})
}));
})
.then(_ => {
console.log(segId);
});

Nothing executes after forEach loop in redux

My redux code looks like following
const getCategories = (res) => (dispatch,getState) => {
let categories = []
const result = res.results
console.log("here")
result.forEach((rawMaterial,index)=>{
if(!_.includes(categories,rawMaterial.category[0].name)){
categories.push(rawMaterial.category[0].name)
dispatch({type:UPDATE_CATEGORIES,categories})
}
});
console.log("here2")
}
Basically it is a function which pushes data into categories array from input parameter "res". What my problem is, "getCategories" function does not executes anything after forEach loop.
The below piece of chunk does not get executed in the above code.
console.log("here2")
Function executes nothing after forEach loop.
Full code
export const getRawMaterials = (params = {}) => (
dispatch,
getState,
{ fetch }
) => {
dispatch({ type: GET_RAW_MATERIALS_REQUEST, params });
const { token } = dispatch(getToken());
const { search, ordering } = getState().rawMaterials;
return fetch(
`/pands/raw-materials/?${qs.stringify({
search,
ordering
})}`,
{
method: "GET",
token,
success: res => {
const rawMaterials = res.results
dispatch({ type: GET_RAW_MATERIALS_SUCCESS, res });
const categories = getCategories(rawMaterials);
dispatch({type:UPDATE_CATEGORIES,categories})
},
failure: err => dispatch({ type: GET_RAW_MATERIALS_FAILURE })
}
);
};
//doubt here, function get return after forEach
const getCategories = (res) => {
let categories = []
const result = res;
return result.map(rawMaterial => {
if(_.includes(categories,rawMaterial.category[0].name)) return null;
return rawMaterial.category[0].name;
}
)
}
My code is not working from the following line:
const categories = getCategories(rawMaterials);
Neither it is giving an error. I think problem is with response coming from backend.
"res" is coming from backend. my data is in "res.results". When I type check "res.results" , it shows object but when I print it, it shows array of objects.

Node.js handle responses from chained promises

I have 3 functions and each of them return a promise. How can I assign the response from each promise to a defined object?
This is my code:
const runResponse = {
webAudits: [],
webJourneys: [],
appJourneys: []
};
webAuditsFailures(req)
.then(
appJourneysFailures(req)
)
.then(
webJourneysFailures(req)
).then(
res.status(201).json({ reports: runResponse })
);
This is what I've tried:
webAuditsFailures(req)
.then(
(response) => {
runResponse.webAudits = response
},
appJourneysFailures(req)
)
.then(
(response) => {
runResponse.appJourneys = response
},
webJourneysFailures(req)
).then(
(response) => {
runResponse.webJourneys = response
},
res.status(201).json({ reports: runResponse })
);
But it doesn't works as expected because the webAuditsFailures is called again even if it didn't end and I don't understand why...
These are other failed attempts to fix this:
Using await
const webAudits = await webAuditsFailures(req);
const appJourneys = await appJourneysFailures(req);
const webJourneys = await webJourneysFailures(req);
runResponse.webAudits = webAudits;
runResponse.webJourneys = webJourneys;
runResponse.appJourneys = appJourneys;
The same thing happens with this:
const webAudits = await webAuditsFailures(req);
runResponse.webAudits = webAudits;
Using the co module:
co(function* () {
var runResponse = yield {
webAudits: webAuditsFailures(req),
webJourneys: appJourneysFailures(req),
appJourneys: webJourneysFailures(req)
};
res.status(201).json({ reports: runResponse });
});
Using Promise.all:
Promise.all([webAuditsFailures(req), appJourneysFailures(req),
webJourneysFailures(req)])
.then(function(allData) {
res.status(201).json({ reports: allData });
});
This is the webAuditsFailures function, which sequentially calls another functions that return a promise
export default async (req) => {
const report = req.body.webAudits;
const def = deferred();
if(report.length > 0) {
var reportList = [];
for(const [reportIndex, item] of report.entries()) {
for(const [runIndex, run] of item.runs.entries()) {
const result = await waComplianceBusiness(req, run.id);
var failureList = [];
if(result.data.overviews) {
const compliance = result.data.overviews[0].compliance;
if(compliance) {
for(const [index, rule] of compliance.entries()) {
const response = await waRuleOverview(req, run.id, rule.id);
const failedConditions = response.data.failedConditions;
const ruleName = response.data.ruleName;
if(response.data.pagesFailed > 0) {
for(const [condIndex, condition] of failedConditions.entries()) {
const request = {
itemId: condition.conditionResult.id,
itemType: condition.conditionResult.idType,
parentId: condition.conditionResult.parentId,
parentType: condition.conditionResult.parentType
}
const body = {
runId: run.id,
ruleId: rule.id,
payload: request
}
waConditionOverview(req, body).done(response => {
// do stuff here
});
}
}
}
if(failureList.length > 0) {
item.runs[runIndex].failures = failureList;
}
}
}
}
}
def.resolve(report);
return def.promise
}
else {
return [];
}
}
This is the problem:
waConditionOverview(req, body).done(response => {
// do stuff here
});
You're performing an async action but not waiting for the result. Don't use the deferred model - use util.promisify for callbacks.
In addition, I warmly recommend not mutating the request/resposne like this but storing the information in objects and returning those.
Here is how you'd write the code:
export default async (req) => {
const report = req.body.webAudits;
if(report.length === 0) return;
const runs = Array.from(report.entries(), ([i, item]) => item.runs.entries());
for (const [_, run] of runs) {
const result = await waComplianceBusiness(req, run.id);
var failureList = [];
if (!result.data.overviews) {
continue;
}
const compliance = result.data.overviews[0].compliance;
if(!compliance) {
continue;
}
for(const [_, rule] of compliance.entries()) {
const response = await waRuleOverview(req, run.id, rule.id);
const { failedConditions, ruleName} = response.data;
if(failureList.length > 0) {
item.runs[runIndex].failures = failureList;
}
if(response.data.pagesFailed === 0) continue;
for(const [_, condition] of failedConditions.entries()) {
const request = {
itemId: condition.conditionResult.id,
itemType: condition.conditionResult.idType,
parentId: condition.conditionResult.parentId,
parentType: condition.conditionResult.parentType
}
const body = { runId: run.id, ruleId: rule.id, payload: request}
const reponse = await waConditionOverview(req, body);
// do stuff here
// update response
// update report, so next time we try it's updated and doesn't return empty;
}
}
}
return report;
}
In a promise chain, the current .then() should return a promise. The result of this promise will be passed to the next .then():
webAuditsFailures(req)
.then((response) => {
runResponse.webAudits = response;
return appJourneysFailures(req); // return a promise
})
.then((response) => { // response contains the result of the promise
runResponse.appJourneys = response;
return webJourneysFailures(req);
})
.then((response) => {
runResponse.webJourneys = response;
res.status(201).json({ reports: runResponse });
});
Depending on what .json() in the last .then() does, you should return that as well if there are other .then()s in the promise chain.

Categories