writeFile does not wait for variable to be instantiated - javascript

I'm new to node.js and javascript in general but I am having issues understanding why the writeFile function is writing a blank file. I think the for loop should be a Promise but I am not sure how to write it.
const removeProviders = function () {
readline.question('Where is the file located? ', function(filePath) {
let providerArray = fs.readFileSync(filePath).toString().split('\r\n');
console.log(providerArray);
let importFile = '';
for (let providerId in providerArray) {
getProvider(providerArray[providerId]).then((response) => {
let providerInfo = response.data;
return providerInfo;
}).then((providerInfo) => {
let entry = createImportEntry(providerInfo);
importFile += "entry";
})
}
fs.writeFile('C:/path/to/file.txt', importFile);

You can collect all the promises from the for-loop into an Array and then pass them into Promise.all() which will resolve only after all of them resolved. If one of the promises are rejected, it will reject as well:
const promises = providerArray.map((item) => {
return getProvider(item)
.then((response) => {
let providerInfo = response.data;
return providerInfo;
})
.then((providerInfo) => {
let entry = createImportEntry(providerInfo);
importFile += "entry";
});
});
Promise.all(promises).then(() => {
fs.writeFile('C:/path/to/file.txt', importFile, err => {
if (err) {
console.error(err);
}
});
});
While doing this you could also get rid of the importFile variable and collect directly the results of your promises. Promise.all().then(results => {}) will then give you an array of all results. Thus no need for an updatable variable.

The below approach may be useful.
I don't know your getProvider return Promise. you can set promise in getProvider method
const getProviderValue = async function(providerArray) {
return new Promise((resolve, reject) {
let importFile = '';
for (let providerId in providerArray) {
await getProvider(providerArray[providerId]).then((response) => {
let providerInfo = response.data;
return providerInfo;
}).then((providerInfo) => {
let entry = createImportEntry(providerInfo);
importFile += "entry";
})
}
resolve(importFile)
})
}
const removeProviders = async function () {
readline.question('Where is the file located? ', function(filePath) {
let providerArray = fs.readFileSync(filePath).toString().split('\r\n');
console.log(providerArray);
let importFile = await getProviderValue(providerArray)
fs.writeFile('C:/path/to/file.txt', importFile);
})
}

Your for loop does not wait for the promises to resolve. A better way to approach this problem would be to use reduce.
providerArray.reduce(
(p, _, i) => {
p.then(_ => new Promise(resolve =>
getProvider(providerArray[providerId]).then((response) => {
let providerInfo = response.data;
return providerInfo;
}).then((providerInfo) => {
let entry = createImportEntry(providerInfo);
importFile += entry;
resolve();
}))
);
}
, Promise.resolve() );
You can also use Promise.all but the out data may not be in the order that you expect if you append to the importFile variable.

Related

How to push failed promises to separate array?

I am looping through ids that sends it to an async function and I want to return both success and failed data, right now I am only returning success data
const successContractSignature: LpContractSla[] = [];
for (const id of lpContractSlaIds) {
const data = await createLpSignature(context, id);
if (data) {
successContractSignature.push(data);
}
}
return successContractSignature;
If the createLpSignature throws error, do i need a try catch here? but wouldnt that exit the loop?
How can I push failed data to separate array without breaking the loop?
Unless there's a specific reason to avoid the call in parallel, it's always a good practice to avoid to start async calls in a for loop with await, since you are going to wait for each promise to resolve (or reject ) before executing the next one.
This is a better pattern which lets you also get all the results of the promises, either they resolved or rejected:
const successContractSignature: LpContractSla[] = await Promise.allSettled(lpContractSlaIds.map((id: string) => createLpSignature(context,id)))
return successContractSignature;
But if for some particular reason you need to make these calls in a sequence and not in parallel, you can wrap the single call in a try catch block ,that won't exit the loop:
for (const id of lpContractSlaIds) {
let data;
try {
data = await createLpSignature(context, id);
} catch(e) {
data = e
}
if (data) {
successContractSignature.push(data);
}
}
You can test it in this example:
const service = (id) =>
new Promise((res, rej) =>
setTimeout(
() => (id %2 === 0 ? res("ID: "+id) : rej('Error ID : '+id)),
1000
)
);
const ids = [1,2,3,4,5]
const testParallelService = async () => {
try {
const data = await Promise.allSettled(ids.map(id => service(id)))
return data.map(o => `${o.status}: ${o.reason ?? o.value}`)
} catch(e) {
console.log(e)
}
}
testParallelService().then(data => console.log("Parallel data: ", data))
const testSequentialService = async () => {
const res = [];
for (const id of ids) {
let data;
try {
data = await service(id);
} catch (e) {
data = e;
}
if (data) {
res.push(data);
}
}
return res;
};
testSequentialService().then((data) => console.log('Sequential Data: ', data));

How to have a function finish a for loop before returning

Below is the code I type to go through an array and stick the data needed into an output array, then ultimately return the output array:
var stocks = ['AAPL', 'BMY']
let getInfo = arr => {
let output = []
arr.forEach(i => {
var root = 'https://fmpcloud.io/api/v3/profile/' + i + '?apikey=myAPIKey'
axios.get(root)
.then((data) => {
output.push(data.data)
})
.catch((e) => {
console.log('error', e)
})
})
console.log('output: ', output)
return output
}
getInfo(stocks)
The console.log just logs an empty array, which makes me think it goes to the return statement before the for loop finishes executing. Does anybody know the best way to have the for loop finish first, and then finally return the output array?
You can use async/await keywords to get the axios call to wait. Like:
let getInfo = async arr => {
let output = []
try {
for(var i in arr) {
var root = 'https://fmpcloud.io/api/v3/profile/' + arr[i] + '?apikey=myAPIKey'
var data = await axios.get(root);
output.push(data.data);
}
}
catch(e) {
// oops
console.log('error', e);
return null;
}
return output;
}
That's because axios is the asynchronous method and return statement should be returned before the axios().then().
Use Promise.all() and use async/await
var stocks = ['AAPL', 'BMY']
let getInfo = async arr => {
let output = []
const promises = []
arr.forEach(i => {
var root = 'https://fmpcloud.io/api/v3/profile/' + i + '?apikey=myAPIKey'
promises.push(axios.get(root))
.then((data) => {
output.push(data.data)
})
.catch((e) => {
console.log('error', e)
})
})
const result = await Promise.all(promises)
output = result.map(r => r.data)
console.log('output: ', output)
return output
}
getInfo(stocks)
Axios.get returns a promise, it is executed asynchronously while the rest of your lambda continues its execution.
Look into Promise.all() so that you can log output after all the promises have resolved.
var stocks = ["AAPL", "BMY"];
let getInfo = async (arr) => {
const output = await Promise.all(
arr.map(
(i) =>
new Promise(async (resolve, reject) => {
try {
const { data } = await axios.get(
`https://fmpcloud.io/api/v3/profile/${i}?apikey=myAPIKey`
);
resolve(data);
} catch (err) {
reject(err);
}
})
)
);
console.log("output: ", output);
return output;
};
getInfo(stocks);
Don't use forEach. Use a for/of loop. Next, use async/await to synchronize on the
axios calls. That should do it.

Function gets trigger before promise is resolved

I have this two functions, writedb is supposed to act only when uploadImages is done. I'm not getting what I'm hoping for.
The resolve shows "null" for "this.imageURL" as is predefined but is triggered before the console.log that are inside the loop which show the correct wanted information.
uploadImages() {
return new Promise(async (resolve, reject) => {
let newImageURL = []
for (const path of this.images){
fs.readFile(path,
async (err, data) => {
const ref = st.ref(`images/${this.id}/${path.split('/').pop()}`);
await ref.put(data);
let url = await ref.getDownloadURL();
newImageURL.push(url);
this.imageURL = newImageURL;
console.log(this.imageURL);
})
}
resolve(console.log(this.name),
console.log(this.imageURL),
console.log('Done upload images'))
})
}
writedb(){
(() => {
let obj = {};
this.uploadImages().then(
console.log('writedb in action...'),
db.collection("products").doc(this.id).set(Object.assign(obj, this))
)
})();
}
What am I doing wrong here? How can i get the promise to resolve only if the for loop is done??
Use fs.promises and Promise.all to wait for each readFile to resolve - and avoid the explicit Promise construction antipattern:
uploadImages() {
return Promise.all(this.images.map(
path => fs.promises.readFile(async (data) => {
const ref = st.ref(`images/${this.id}/${path.split('/').pop()}`);
await ref.put(data);
return ref.getDownloadURL();
})
));
}
Then instance.uploadImages() will return a Promise that resolves to an array of the data you want. uploadImages().then((result) => { /* do stuff with result */ }).catch(handleErrors);
With CertainPerformance help I've corrected my code as follows
Thank you! After some though I finally was able to implement it and achieve the expected result.
uploadImages (){
return Promise.all(this.imagespath.map(
path => fs.promises.readFile(path)
.then(async (data)=>{
const ref = st.ref(`images/${this.id}/${path.split('/').pop()}`);
await ref.put(data);
return ref.getDownloadURL();
})
))
}
writedb(){
this.uploadImages()
.then((res)=>{
this.imageURL = res;
//Firebase no toma custom objects Object.assign(obj, this)
let obj ={}
Object.assign(obj, this);
delete obj.imagespath;
db.collection("products").doc(this.id).set(obj)
}).catch( err=>console.log(err) )
}

How to rewrite a function using Promises.all to async and await

I have a function that takes an array of URLs and downloads them. It looks like this:
const loadFiles = filePaths => {
return new Promise((resolve, reject) => {
let fetchPromises = filePaths.map(filePath => fetch(filePath))
Promise.all(fetchPromises).then(fetchResolves => {
let textPromises = []
fetchResolves.forEach(fetchResolve => {
if (!fetchResolve.ok) {
return reject(`${fetchResolve.statusText} : ${unescape(fetchResolve.url)}`)
}
textPromises.push(fetchResolve.text())
})
Promise.all(textPromises).then(resolve)
})
})
}
export {loadFiles}
The issue I am having is that the multiple calls to Promise.all start to resemble callbacks even though I am using promises.
Is there a way to map the functionality of Promise.all to async and await to simplify this function?
How do I do this?
You can await Promise.all just fine. Example:
async function loadFiles(filePaths) {
let fetchPromises = filePaths.map(filePath => fetch(filePath))
let fetchResolves = await Promise.all(fetchPromises)
let textPromises = []
fetchResolves.forEach(fetchResolve => {
if (!fetchResolve.ok) {
throw new Error(`${fetchResolve.statusText} : ${unescape(fetchResolve.url)}`)
}
textPromises.push(fetchResolve.text())
})
return Promise.all(textPromises)
}

missing timing from promised value

So I am using Forge with View API to analyze all parts from a model which contain concrete and hide everything that is not concrete. The problem is that the properties for checking concrete are called from a DB which requires me to make it a promise. Checking for concrete is working as expected and then the problem starts. I return the Ids containing concrete, but my function which is supposed to hide it uses the Ids before the promise is resolved, so the array is empty.
console.log logs it as expected but everything else misses the timing.
My code:
getProperties(dbId) {
return new Promise((resolve, reject) => {
this.gui.getProperties(
dbId,
args => {
resolve(args.properties)
},
reject
)
})
}
async getConcreteIds() {
let wallfloorids = this.getWallIds().concat(this.getFloorIds());
let concreteIds = [];
for (let id of wallfloorids) {
let p1 = this.view.getProperties(id);
p1.then(props => {
for (let prop of props) {
if (prop.displayCategory === "Materialien und Oberflächen" && prop.displayValue.contains("Concrete")) {
concreteIds.push(id);
}
}
}).catch(() => {
});
}
return new Promise((resolve, reject) => {
try {
resolve(concreteIds)
} catch (e) {
console.log("Err", reject)
}
})
}
async onlyConcrete() {
this.getConcreteIds().then(concrete => {
debugger;
this.viewer.isolateById(concrete)
});
}
Map an array of promises for your loop and use Promise.all() to resolve after all the promises in loop resolve
Something like:
async getConcreteIds() {
let wallfloorids = this.getWallIds().concat(this.getFloorIds());
let concreteIds = [];
let promises = wallfloorids.map(id => {
let p1 = this.view.getProperties(id);
return p1.then(props => {
for (let prop of props) {
if (prop.displayCategory === "Materialien und Oberflächen" && prop.displayValue.contains("Concrete")) {
concreteIds.push(id);
}
}
});
});
return Promise.all(promises)
.then(_ => concreteIds)
.catch(err => console.log("Err", err))
}

Categories