Nested promises with for loop doesn't work - javascript

I have this nested loop of promises and at then end a for loop that pushes items in the files array.
public async content() {
let files = [];
return new Promise(async (resolve, reject) => {
await this.axios.get(this.currentRequest).then(async biosample => {
await this.axios.get(this.currentRequest + biosample.data.item).then(async datasets => {
for (let i = 0; i < datasets.data.Items.length; i++) {
await this.axios.get(this.currentRequest + datasets.data.Items[i].Id).then(response => {
files.push(response.data.Item);
}).catch(reason => {
reject(reason)
});
}
})
}).catch(function (error) {
reject(new Error(error.response))
});
resolve(files)
})
}
The calls are made correctly, because if I use Promise.all([promises here]), then it works. But I'm trying to learn to chain promises properly.
When I'm debugging with webstorm datasets seems to be defined and have the necessary properties.

Schematically you code must like this
content() {
return Promise.resolve()
.then(_ => this.axios.get(this.currentRequest)
.then(biosample => this.axios.get(this.currentRequest + biosample.data.item))
.then(datasets => Promise.all(datasets.data.Items.map(item => this.axios.get(this.currentRequest + item.Id))))
}

You're not using the potential of await. Your code can be as simple at this:
public async content() {
let files = [];
return new Promise(async (resolve, reject) => {
try {
let biosample = await this.axios.get(this.currentRequest)
let datasets = await this.axios.get(this.currentRequest + biosample.data.item)
for (let i = 0; i < datasets.data.Items.length; i++) {
let response = await this.axios.get(this.currentRequest + datasets.data.Items[i].Id)
files.push(response.data.Item);
}
resolve(files)
} catch(error) {
reject(new Error(error.response || error))
};
})
}

Related

Maintain Order In Async Function Node JS

I am new to NodeJs.
I want to push elements in files array in order of url. But i am getting random order .
Below is the code for same. Can anyone suggest what i am doing wrong here.
const mergePdf =async (urlArray, files) => {
for (let i = 0; i < urlArray.length; i++) {
try {
const pdfBytes = await fetch(urlArray[i]).then((res) => {
return res.arrayBuffer();
});
let bytes = new Uint8Array(pdfBytes);
files[i]=bytes;
} catch (err) {
console.log(err);
}
}
}
write seprate function of your api call in promises like
const other_func = (url) => {
return new Promise(async (resolve) => {//updated line
try {
const pdfBytes = await fetch(url).then((res) => {
let bytes = new Uint8Array(pdfBytes);
resolve(bytes);// return value pass in resolve method [Updated]
});
} catch (err) {
console.log(err);
}
})
}
and in your original function call this function using await
like
const mergePdf =async (urlArray, files) => {
for (let i = 0; i < urlArray.length; i++) {
files[i] = await other_func(urlArray[i])
}}

writeFile does not wait for variable to be instantiated

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.

issue with async await

I am trying to make two block of code to run sequentially with async and await
I have the code in stackblitz, open the console on Chrome to see the trace
let isMomHappy = true;
function willIgotNewPhone () {
return new Promise( (resolve, reject) => {
if (isMomHappy) {
const phone = {
brand: 'samsung',
color : 'black'
};
resolve(phone);
} else {
const reason = new Error('mom not happy');
reject(reason);
}
});
}
async function showOff(phone) {
return new Promise( (resolve, reject) => {
const message = ' Hey Friend I show my phone ' + phone.brand;
resolve(message);
});
}
async function askMom() {
return await new Promise( async (resolve) => {
console.log('before asking Mom'); // log before
await willIgotNewPhone()
.then( function(fullfilled) {
console.log('Got phone from mom ' + JSON.stringify(fullfilled));
})
.catch( function(error) {
console.log(error.message);
});
console.log('after asking Mom'); // afeter log
resolve('END');
});
}
let data: any[] = [];
async function createData() {
return new Promise( (resolve, reject) => {
for (let index = 0; index < 500000; index++) {
const element: any = {};
element.id = index;
element.name = '' + index;
data.push(element);
}
if (data && data.length > 0) {
console.log(' ====== creating data size=%s', data.length);
resolve(data);
} else {
reject( new Error(' ==== Creating data Error : empty'));
}
});
}
async function callCreateData() {
return new Promise( async (resolve) => {
console.log(' ======before creating data');
await createData().then( (dataReturn: any[]) => {
console.log(' ----datareturn length=%s', dataReturn.length);
});
console.log(' ======after creating data');
});
}
async function callingMom() {
await askMom().then( (str) => {
console.log(str);
});
}
callingMom();
callCreateData();
I am calling 2 functions this.callingMom() and this.callCreateData();
and expecting the traces of the 2 functions to be sequential
I was expecting the following output
before asking Mom
Got phone from mom {"brand":"samsung","color":"black"}
after asking Mom
=====before creating creating data
===== creating data size=500000
----datareturn length=500000
===== after creating creating data
but I got the output :
before asking Mom
======before creating data
====== creating data size=500000
Got phone from mom {"brand":"samsung","color":"black"}
----datareturn length=500000
======after creating data
after asking Mom
END
Any idea what is my problem ?
Thanks
async functions can be used for two things, primarilly: to return a Promise, and to be able to use the await keyword inside. If you're not using await, or if the only await you're using is the Promise that's going to be returned, there's no point in having an async function at all - just use a normal function that returns a Promise, like in your willIgotNewPhone.
Also, in order to chain promises together, you need to use then. Simply calling asynchronous functions right after the other won't cause the thread to pause until they're done. Like this:
callingMom().then(callCreateData);
let isMomHappy = true;
function willIgotNewPhone() {
return new Promise((resolve, reject) => {
if (isMomHappy) {
const phone = {
brand: 'samsung',
color: 'black'
};
resolve(phone);
} else {
const reason = new Error('mom not happy');
reject(reason);
}
});
}
function showOff(phone) {
return new Promise((resolve, reject) => {
const message = ' Hey Friend I show my phone ' + phone.brand;
resolve(message);
});
}
function askMom() {
return new Promise(async (resolve) => {
console.log('before asking Mom'); // log before
await willIgotNewPhone()
.then(function(fullfilled) {
console.log('Got phone from mom ' + JSON.stringify(fullfilled));
})
.catch(function(error) {
console.log(error.message);
});
console.log('after asking Mom'); // afeter log
resolve('END');
});
}
let data = [];
function createData() {
return new Promise((resolve, reject) => {
for (let index = 0; index < 500000; index++) {
const element = {};
element.id = index;
element.name = '' + index;
data.push(element);
}
if (data && data.length > 0) {
console.log(' ====== creating data size=%s', data.length);
resolve(data);
} else {
reject(new Error(' ==== Creating data Error : empty'));
}
});
}
function callCreateData() {
return new Promise(async (resolve) => {
console.log(' ======before creating data');
await createData().then((dataReturn) => {
console.log(' ----datareturn length=%s', dataReturn.length);
});
console.log(' ======after creating data');
});
}
function callingMom() {
return askMom().then((str) => {
console.log(str);
});
}
callingMom().then(callCreateData);

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

Node JS: chaining promises which are using promises

I need to chain promises which are using request promises, so it is kinda chaining nested promises.
Imagine the code:
const rp = require('request-promise');
function doStuff(){
for ( let i = 0; i <= 10; i++ ){
methodA();
}
};
function methodA(){
let options = {...};
rp(options)
.then(result => methodB(result))
.catch(err => console.log(err));
};
function methodB(resultA){
let options = {uri: resultA};
rp(options)
.then(result => methodC(resultA, result))
.catch(err => console.log(err));
};
function methodC(resultA, resultB){
//some calculations
};
In doStuff I need to wait for result of all ten executions of methodC and collect them into array. I have tried to chain it like that:
function doStuff(){
for ( let i = 0; i <= 10; i++ ){
let promiseA = new Promise((resolve, reject) => {
resolve(methodA());
});
let promiseB = promiseA.then(result => methodB(result));
let promiseC = promiseB.then(result => methodC(promiseA.result, result));
Promise.all([promiseA, promiseB, promiseC]);
}
};
But for sure it won't work, because in methodA and methodB we have HTTP requests which are asynchronous. Therefore, result in promiseB is undefined.
It means, the question is: how to chain promises, if they have nested promises? (and how to collect result in the end?)
Thanks!
UPDATE: Chaining promises also not much of the help, as 1 is returned prior array of AB's, but desired result is vice versa:
function methodA(){
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Resolved A');
resolve('A');
}, Math.random() * 2000);
});
return promise
.then(result => methodB(result))
.catch(err => console.log(err));
}
function methodB(resultA){
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Resolved B');
resolve('B');
}, Math.random() * 2000);
});
return promise
.then(result => methodC(resultA, result))
.catch(err => console.log(err));
}
function methodC(resultA, resultB){
return resultA + resultB;
}
function doStuff() {
let promises = [];
for (let i = 0; i <= 10; i++){
promises.push(methodA());
}
Promise.all(promises).then(results => {
console.log(results);
});
return 1;
}
console.log(doStuff());
Each of your functions needs to return their promise:
function methodA(){
let options = {...};
return rp(options)
.then(result => methodB(result))
.catch(err => console.log(err));
}
function methodB(resultA){
let options = {uri: resultA};
return rp(options)
.then(result => methodC(resultA, result))
.catch(err => console.log(err));
}
function methodC(resultA, resultB){
//some calculations
}
function doStuff() {
let promises = [];
for ( let i = 0; i <= 10; i++ ){
promises.push(methodA());
}
Promise.all(promises).then(...)
}
Edit: I created a test example, which creates promises in methodA and methodB. Each promise lasts some amount of time from 0 to 2 seconds. It seems to be working:
function methodA(){
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Resolved A');
resolve('A');
}, Math.random() * 2000);
});
return promise
.then(result => methodB(result))
.catch(err => console.log(err));
}
function methodB(resultA){
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Resolved B');
resolve('B');
}, Math.random() * 2000);
});
return promise
.then(result => methodC(resultA, result))
.catch(err => console.log(err));
}
function methodC(resultA, resultB){
return resultA + resultB;
}
function doStuff() {
let promises = [];
for (let i = 0; i <= 10; i++){
promises.push(methodA());
}
return Promise.all(promises).then(results => {
console.log(results);
return 1;
});
}
doStuff().then(result => {
console.log(result);
});
Answer by #Frank_Modica is the way to go.
Just want to add that if you enable async/await you could do it like this. But it does require Babel or Typescript:
async function myMethod() {
const options = { }
try {
const result_a = await rp(options)
const result_b = await rp({ uri: result_a })
const result_c = ...
} catch(err) {
console.log(err)
}
}
for (let i = 0; i <= 10; i++) {
await myMethod();
}
It has to be lightly changed to:
Promise.all([promiseA, promiseB, promiseC]).then([promiseD]);
Also in the functions itself it has to be a return statement to make them chained. For the request itself, just add: {async: false}
Also it can be used:
var runSequence = require('run-sequence');
function methodA () {
return runSequence(
'promiseA',
'promiseB',
['promiseC1', 'promiseC2'],
'promiseD',
callback
);
}

Categories