First of all I am a newbie in nodejs, please be indulgent :)
I have a problem with some async functions. In my case, a first API call is made and then a sub call is made on every item returned. this is my code after the first call is made :
const countryStatesCalls: Promise<any>[] = [];
countries.forEach((country, index) => {
countries[index] = convertToCountry(country);
const countryStatesCall = (countryId: number) => {
return new Promise((resolve, reject) => {
const countryStatesList = getStateCountries(countryId);
if (countryStatesList) {
if (Object.prototype.toString.call(countryStatesList) !== '[object Array]') {
resolve([]);
} else {
resolve(countryStatesList);
}
} else {
reject([]);
}
});
};
countryStatesCalls.push(
countryStatesCall(countries[index].id).then(countryStates => {
countries[index].states = countryStates;
}).catch(countryStatesType => {
countries[index].states = countryStatesType;
})
);
});
await Promise.all(countryStatesCalls).then(_ => {
res.json(countries)
});
the following code
res.json(countries)
is supposed to be executed after all promises execution has been finished.
EDIT : the getStateCountries async function :
const getStateCountries = async (countryId: number, lang?: string) => {
const listState = await odoo.searchRead(
'res.country.state',
{
filters: [
{
field: 'country_id',
operator: '=',
value: countryId,
},
],
fields: fieldState,
},
{ lang }
);
if (!listState.length) {
return [];
} else {
listState.forEach((state, index) => {
listState[index] = convertToState(state);
});
return listState;
}
};
the main function is an express controller
I think my mistake may be obvious but can you help me please, I read many topics and I cannot see what am I doing wrong.
Thank you in advance
[SOLUTION]
As pointed out by comments, I was doing it the wrong way around, my call on promise called another promise: the solution was to write :
const countryStatesCalls: Promise<any>[] = [];
for (let index = 0; index < countries.length; index++) {
countries[index] = convertToCountry(countries[index]);
if (withSates) {
countryStatesCalls.push(
getStateCountries(countries[index].id)
);
} else {
countries[index].states = [];
}
}
if (withSates) {
await Promise.all(countryStatesCalls).then((countriesStates) => {
countries.forEach((country, index) => {
country.states = [];
for (const countryStates of countriesStates) {
if (countryStates.length
&& countryStates[0].country_id.code === country.id) {
countries[index].states = countryStates;
}
}
});
res.json(countries);
});
} else {
res.json(countries);
}
thank you everyone
I can't exactly tell why it doesn't work, but this looks a bit like the Promise constructor antipattern. The getStateCountries function is async and already creates a promise, so you need to await it or chain .then() to it. The new Promise is not necessary.
Also I'd recommend to avoid forEach+push, rather just use map, and get rid of the countryStatesCall helper:
const countryStatesCalls: Promise<any>[] = countries.map((country, index) => {
countries[index] = convertToCountry(country);
return getStateCountries(countries[index].id).then(countryStatesList => {
if (countryStatesList) {
if (Object.prototype.toString.call(countryStatesList) !== '[object Array]') {
return [];
} else {
return countryStatesList;
}
} else {
throw new Error("getStateCountries() resolved to nothing");
}
}).then(countryStates => {
countries[index].states = countryStates;
});
});
await Promise.all(countryStatesCalls).then(_ => {
res.json(countries)
});
Related
We have this in our code:
const sqlUpsertRecommendations = recommendationsArray => {
// eslint-disable-next-line no-new
new Promise(resolve => {
const insertStatus = {
....
};
if (!recommendationsArray.length > 0) {
resolve({
error: false,
results: insertStatus,
localMessage: 'recommendationsArray.length = 0',
});
}
insertStatus.arrayEmpty = false;
dbConfig.transaction(txn => {
for (let i = 0; i < recommendationsArray.length; i++) {
const {
id,
recommendation_attribute,
recommendation_value,
segment_id,
target_attribute,
target_value,
is_deleted,
updated_at,
name,
image,
border_color,
} = recommendationsArray[i];
const params = [
id,
recommendation_attribute,
recommendation_value,
segment_id,
target_attribute,
target_value,
is_deleted,
updated_at,
name,
image,
border_color,
];
try {
txn.executeSql(
upsertRecommendations,
params,
(_tx, _results) => {
resolve({ error: false, results: insertStatus });
},
error => {
crashlytics().recordError(error);
if (__DEV__) {
console.log('sqlUpsertRecommendations error:', error);
}
const { notInserted } = insertStatus;
insertStatus.inserted = notInserted + 1;
if (1 + i === recommendationsArray.length) {
resolve({ error: false, ...error, results: insertStatus });
}
},
);
} catch (e) {
crashlytics().recordError(e);
}
}
});
});
};
I am learning async await and people in our team are using it like
const testFunction = async () => {
....
await sqlUpsertRecommendations(parsedData)
But vscode is saying
'await' has no effect on the type of this expression.
I just need idea how to best approach and use this kind of code?
You need to actually return the new Promise, then you can await it. If you don't, you just start a Promise and then immediately return undefined, and you can't await undefined.
const sqlUpsertRecommendations = recommendationsArray => {
// eslint-disable-next-line no-new
return new Promise(resolve => {
// ... rest of code
async is really just syntactic sugar for creating and using Promises. An async function just returns a new Promise and that's what await cares about, so await will work if you have a normal function that returns a Promise.
So I have this code here triggered using click on HTML page:
public salaryConfirmation() {
const matDialogConfig: MatDialogConfig = _.cloneDeep(GajiIdSettings.DIALOG_CONFIG);
this.warningNameList = [];
for(let i=0; i < this.kelolaDataPenggajianInfoDataKaryawanList.length; i++) {
const positionClassId = this.selectedKaryawanAllData[i].position.positionClass.id;
const beginYearMonth = this.inputForm.get('bulanBerlaku').value;
const gajiPokok = this.kelolaDataPenggajianInfoDataKaryawanList[i].gaji;
this.structureAndSalaryScaleValidationService.getSalaryRange(positionClassId, beginYearMonth, gajiPokok)
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe(
async (result) => {
this.uiBlockService.hideUiBlock();
if(result.status == 'warning') {
if(result.warnings[0].code == 'trxMutasiKaryawan.confirmation.alert') {
await this.warningNameList.push(this.kelolaDataPenggajianInfoDataKaryawanList[i]);
}
}
},
(error) => {
this.uiBlockService.hideUiBlock();
this.contentAlertService.error(error.errors);
},
() => { this.uiBlockService.hideUiBlock(); }
)
}
matDialogConfig.data = this.warningNameList;
console.log("this.warningNameList.length :", this.warningNameList.length);
if (this.warningNameList.length > 0) {
this.save();
} else {
this.inputMassalGajiWarningComponentDialogRef = this.dialog.open(InputMassalGajiWarningComponent, matDialogConfig);
this.inputMassalGajiWarningComponentDialogRef.afterClosed().subscribe(
(confirm: boolean) => {
if (confirm) {
this.save();
}
}
);
}
}
The problem is, I tried to catch the length of this.warningNameList variable. But it always shows "0" on the result.
I know the problem is because this should be work in asynchronously. But I don't know how to apply it in typescript. Been search this case but I always failed to apply. Anyone can help me out?
I already put await inside the loop, but seems it's not working because of wrong placement.
Some reference that I found out there is this => JavaScript async and await in loops
Thanks in advance
Putting await inside loop is not gonna help you. as it is running in a different context already.
What you need to do is probably chain this 2 operations one after another after using promise instead of observable here.
you can probably do something like this,
public async salaryConfirmation() {
const matDialogConfig: MatDialogConfig = _.cloneDeep(GajiIdSettings.DIALOG_CONFIG);
this.warningNameList = [];
for(let i=0; i < this.kelolaDataPenggajianInfoDataKaryawanList.length; i++) {
const positionClassId = this.selectedKaryawanAllData[i].position.positionClass.id;
const beginYearMonth = this.inputForm.get('bulanBerlaku').value;
const gajiPokok = this.kelolaDataPenggajianInfoDataKaryawanList[i].gaji;
let result = await this.structureAndSalaryScaleValidationService.getSalaryRange(positionClassId, beginYearMonth, gajiPokok)
.pipe(takeUntil(this.ngUnsubscribe)).toPromise();
// Transform following data
// .subscribe(
// async (result) => {
// this.uiBlockService.hideUiBlock();
// if(result.status == 'warning') {
// if(result.warnings[0].code == 'trxMutasiKaryawan.confirmation.alert') {
// await this.warningNameList.push(this.kelolaDataPenggajianInfoDataKaryawanList[i]);
// }
// }
// },
// (error) => {
// this.uiBlockService.hideUiBlock();
// this.contentAlertService.error(error.errors);
// },
// () => { this.uiBlockService.hideUiBlock(); }
// )
}
matDialogConfig.data = this.warningNameList;
console.log("this.warningNameList.length :", this.warningNameList.length);
if (this.warningNameList.length > 0) {
this.save();
} else {
this.inputMassalGajiWarningComponentDialogRef = this.dialog.open(InputMassalGajiWarningComponent, matDialogConfig);
this.inputMassalGajiWarningComponentDialogRef.afterClosed().subscribe(
(confirm: boolean) => {
if (confirm) {
this.save();
}
}
);
}
}
Just like above code convert observables to promise, don't subscribe them.
This way you can use async await syntax with obervables as well, although find out how to handle errors this way.
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))
}
I'm having yet another async issue where I'm lost and have no idea where or how to fix it. Forgive my bad naming.
api call to twitch api and returns an array its results.
exports.batchPromiseWrapper = function(arr) {
const filteredMungedDataArr = [];
let promiseBatachArray = arr.map(vod_id => {
var url = `https://api.twitch.tv/kraken/videos/${vod_id.id}/markers`;
var params = { api_version: 5 };
return axios
.get(url, {
params: params,
headers: {
"Client-ID": "xxxxxxxxxxxxxxx"
}
})
.then(res => {
return res.data;
})
.catch(function(error) {
console.log(error);
});
});
return Promise.all(promiseBatachArray)
.then(markers => {
if (markers !== null) {
markers.map(markerObj => {
if (markerObj.markers.game_changes !== null) {
markerObj.markers.game_changes.forEach(gameName => {
if (gameName.label === "Fortnite") {
filteredMungedDataArr.push(markerObj);
}
});
}
});
return filteredMungedDataArr;
}
})
.catch(err => {
if (err.status === 500 || err.status === 404) {
console.log("error: ", err, err.message);
}
});
};
The data looks like this:
[[1,2,3,4,5],[1,2,3,4,5]], generator will yield and make a promise.all call of 5 before pausing 5sec and continuing to the next batch of 5.
exports.batchFetchingGeneratorWrapper = function(generator, batchArray) {
let evalNextValue = generator.next();
let delay = (v, t) => {
return new Promise(resolve => {
setTimeout(resolve.bind(null, v), t);
});
};
if (!evalNextValue.done) {
exports.batchPromiseWrapper(evalNextValue.value).then(data => {
let newBatchArray = batchArray;
if (data !== undefined) {
newBatchArray = batchArray.concat(data);
}
delay(5000).then(() => {
exports.batchFetchingGeneratorWrapper(generator, newBatchArray);
});
});
} else {
console.log("yay done!", batchArray);
return batchArray;
}
};
I'm able to console the results in batchArray from batchFetchingGeneratorWrapper, but I unable to act on it and I know it has something to do with async and how it has yet to be resolved.
promiseDataWrapper
.then(data => {
return gatherData.cleanUpVODData(data);
})
.then(data => {
function* batchFetching(batchArray) {
for (let i = 0; i < batchArray.length; i++) {
yield batchArray[i];
}
}
let batchArrResult = [];
let g = batchFetching(data);
new Promise((resolve, reject) => {
gatherData.batchFetchingGeneratorWrapper(g, batchArrResult);
if (g.done) { // i dont think this works
console.log("batchArrResult 1: ", batchArrResult);
resolve(batchArrResult);
}
}).then(result => console.log("asdfasdf", batchArrResult)); // empty array is returned
});
As far as I can tell, the problem lies chiefly in batchFetchingGeneratorWrapper().
It should be a matter of :
fixing delay()
making appropriate returns to make the recursion work
ensuring that the function returns Promise.
Almost undoubtedly (syntactically) simpler with async/await but here it is with old-fashioned thens :
exports.batchFetchingGeneratorWrapper = function(generator, batchArray) {
let evalNextValue = generator.next();
let delay = (t) => {
return new Promise(resolve => {
setTimeout(resolve, t);
});
};
if (!evalNextValue.done) {
return exports.batchPromiseWrapper(evalNextValue.value).then(data => {
return delay(5000).then(() => {
return exports.batchFetchingGeneratorWrapper(generator, batchArray.concat(data || []));
});
});
} else {
console.log("yay done!", batchArray);
return Promise.resolve(batchArray); // <<< promise wrapped to ensure that batchFetchingGeneratorWrapper() returns Promise
}
};
And chain the batchFetchingGeneratorWrapper() call appropriately :
promiseDataWrapper
.then(data => gatherData.cleanUpVODData(data))
.then(data => {
function* batchFetching(batchArray) {
for (let i = 0; i < batchArray.length; i++) {
yield batchArray[i];
}
}
return gatherData.batchFetchingGeneratorWrapper(batchFetching(data), []).then(batchArrResult => {
console.log('batchArrResult: ', batchArrResult);
return batchArrResult;
});
}).catch(error => {
console.log(error);
});
I am trying to process a queue, in parallel. I have an object array, and I need to apply function func to each element, in parallel. I am making the parallelism level of chains as below:
async function() {
const pickUpNextTask = () => {
if (this.internalQueue.length) {
return this.func(this.internalQueue.shift())
}
}
const startChain = () => {
return Promise.resolve().then(function next() {
console.log('before then(next)')
return pickUpNextTask().then(next)
})
}
let chains = []
for (let k = 0; k < this.parallelism; k += 1) {
chains.push(startChain())
}
await Promise.all(chains)
this.drain()
}
It is not working like I want to. The pickUpNextTask() ends-up returning undefined when the queue is empty, so there is no then.
How does one deal with this?
Turned out, it was easy enough:
const pickUpNextTask = () => {
if (this.internalQueue.length) {
return this.func(this.internalQueue.shift())
} else {
return Promise.reject()
}
}
const startChain = () => {
return Promise.resolve()
.then(function next() {
return pickUpNextTask()
.then(next)
.catch(() => {
return Promise.resolve()
})
})
}