Run Promise all in an await for loop - javascript

I have a nested array of Promise function. Eg:
let callSet = [
[ func1 , func2 , func3],
[ func4 , func5 , func6],
[ func7 , func8 , func9],
]
the response of await func1() will be in below structure:
{
data : [ {id: 1} , {id:2}],
status: 400
}
I want to run it in a for loop so that they run in batch sequentially and incrementally load the data into array as they come in. Tried below code but I am lost on how to do it:
const finalData = [];
private asyncForEach(PromiseGroups): Promise<any> {
return PromiseGroups.reduce(async (acc, cItem) => {
const results = await acc;
const res = await Promise.all(cItem) as any;
finalData = [...finalData , ...[].concat(...res.map(r => r.data))];
return results
}, Promise.resolve([]))
}
I would like to load it in as:
[ {id: 1}, {id:2} , {id: 3} ..... ]
and this should get updated as the Promise all is getting resolved
I want to wait till func1 , func2 , func3 is resolved and then move to func4 , func5 , func6 . and once I get data of func4 , func5 , func6 , I want to push it with the data of func1 , func2 , func3

Try the below code:
private init(){
let callSet = [
[ func1 , func2 , func3],
[ func4 , func5 , func6],
[ func7 , func8 , func9],
];
this.asyncForEach(callSet,this.fetchInBatch.bind(this) )
}
private asyncForEach(funcGrpList, execFunc) {
return funcGrpList.reduce((p,funcList) => {
return p.then(() => execFunc(funcList));
}, Promise.resolve());
}
private fetchInBatch(pageGroupList) {
return Promise.all(pageGroupList).then((res: any) => {
this.finalData = [...this.finalData , ...[].concat(...res.map(r => r.data))];
})
}
This should work as expected

Edit: Assuming the last array result is return directly because there is no need to wait for next loop to be finished.
async function run(callSet) {
const output = [];
let prev = [];
const len = callSet.length;
for (let i = 0; i < len; i++) {
const array = await Promise.all(callSet[i].map(func => func()));
const data = array.map(item => item.data);
if (i === 0) {
// no need to append item to output
// just append item to previous array for next loop to use.
prev.push(...data);
} else if (i < len) {
// append item to output from previous result.
output.push(...prev);
prev = [];
// append data to previous result for next loop.
prev.push(...data);
} else {
//last loop, just append data from previous result and current result
output.push(...prev);
output.push(...data);
}
}
console.log(output);
}

This will call the execution sets in the requested order and timing and add the return data as soon as the group of promises returns
const finalData = [];
async function execute() {
for (const set of callSet) {
const resp = await Promise.all(set.map(f => f()));
finalData.push(...resp.map(r => r.data).flat());
}
}
Once execute() is called finalData will be updated asynchronously once for each 'row' of functions.
For future readers
resp.map(r => r.data).flat() is due to the specified promises payload. If someone needs just to pack the results together the code would be:
for (const set of callSet) {
const resp = await Promise.all(set);
finalData.push(...resp);
}

If you want to load the data in chunks but produce flat array of results, your easiest option is to use async/await syntax:
interface Data {
id: number
}
interface DataResponse {
data: Data[];
status: number;
}
type AsyncCall = () => Promise<DataResponse>;
/* ... */
const result: Data[] = [];
for(const chunk of callSet) {
const chunkResult = await Promise.all(chunk.map(f => f()));
result.push(...chunkResult.flatMap(x => x.data));
}
Playground Link
JavaScript demo:
/* mock section */
const fakeFunc = (id1, id2) => ()=>
Promise.resolve({
data : [{id: id1} , {id: id2}],
status: 400
});
const func1 = fakeFunc(1, 2),
func2 = fakeFunc(3, 4),
func3 = fakeFunc(5, 6),
func4 = fakeFunc(7, 8),
func5 = fakeFunc(9, 10),
func6 = fakeFunc(11, 12),
func7 = fakeFunc(13, 14),
func8 = fakeFunc(15, 16),
func9 = fakeFunc(17, 18)
;
/* /mock section */
async function main() {
let callSet = [
[ func1 , func2 , func3],
[ func4 , func5 , func6],
[ func7 , func8 , func9],
];
const result = [];
for(const chunk of callSet) {
const chunkResult = await Promise.all(chunk.map(f => f()));
result.push(...chunkResult.flatMap(x => x.data));
}
return result;
}
main()
.then(r => console.log(r));
I you prefer to use the Promise API only, instead of async/await, then you can reduce into a promise like this:
interface Data {
id: number
}
interface DataResponse {
data: Data[];
status: number;
}
type AsyncCall = () => Promise<DataResponse>;
/* ... */
const result = callSet.reduce((p: Promise<Data[]>, chunk: AsyncCall[]) =>
p.then(acc =>
Promise.all(chunk.map(f => f()))
.then(chunkResult => acc.concat(chunkResult.flatMap(x => x.data))))
, Promise.resolve([]));
Playground Link
JavaScript demo:
/* mock section */
const fakeFunc = (id1, id2) => () =>
Promise.resolve({
data : [{id: id1} , {id: id2}],
status: 400
});
const func1 = fakeFunc(1, 2),
func2 = fakeFunc(3, 4),
func3 = fakeFunc(5, 6),
func4 = fakeFunc(7, 8),
func5 = fakeFunc(9, 10),
func6 = fakeFunc(11, 12),
func7 = fakeFunc(13, 14),
func8 = fakeFunc(15, 16),
func9 = fakeFunc(17, 18)
;
/* /mock section */
function main() {
let callSet = [
[ func1 , func2 , func3],
[ func4 , func5 , func6],
[ func7 , func8 , func9],
];
const result = callSet.reduce((p, chunk) =>
p.then(acc =>
Promise.all(chunk.map(f => f()))
.then(chunkResult => acc.concat(chunkResult.flatMap(x => x.data))))
, Promise.resolve([]));
return result;
}
main()
.then(r => console.log(r));
However, it is a bit ugly. It can be improved by extracting some functions:
interface Data {
id: number
}
interface DataResponse {
data: Data[];
status: number;
}
type AsyncCall = () => Promise<DataResponse>;
/* ... */
const combineWith = (acc: Data[]) => (chunkResult: DataResponse[]) =>
acc.concat(chunkResult.flatMap(x => x.data));
const process = (chunk: AsyncCall[]) => (acc: Data[]) =>
Promise.all(chunk.map(f => f()))
.then(combineWith(acc));
/* ... */
const result = callSet.reduce((p: Promise<Data[]>, chunk: AsyncCall[]) =>
p.then(process(chunk))
, Promise.resolve([]))
Playground Link
JavaScript demo:
/* mock section */
const fakeFunc = (id1, id2) => () =>
Promise.resolve({
data : [{id: id1} , {id: id2}],
status: 400
});
const func1 = fakeFunc(1, 2),
func2 = fakeFunc(3, 4),
func3 = fakeFunc(5, 6),
func4 = fakeFunc(7, 8),
func5 = fakeFunc(9, 10),
func6 = fakeFunc(11, 12),
func7 = fakeFunc(13, 14),
func8 = fakeFunc(15, 16),
func9 = fakeFunc(17, 18)
;
/* /mock section */
const combineWith = (acc) => (chunkResult) =>
acc.concat(chunkResult.flatMap(x => x.data));
const process = (chunk) => (acc) =>
Promise.all(chunk.map(f => f()))
.then(combineWith(acc));
function main() {
let callSet = [
[ func1 , func2 , func3],
[ func4 , func5 , func6],
[ func7 , func8 , func9],
];
const result = callSet.reduce((p, chunk) =>
p.then(process(chunk))
, Promise.resolve([]));
return result;
}
main()
.then(r => console.log(r));

Related

Await doesn't doing the job

I got this map function on a array:
foldersTreeImagesFiles.map( async (paragliderFolder) => {
const pathfolder = pathImagesParaglider + '/' + paragliderFolder.subfolderName;
const imagesUrl = await paragliderFolder.ImagesParagilder.reduce( async (UrlsImage, image, index) => {
const pathImage = pathfolder + '/' + image;
const folderCoulidinary = 'paraglider/' + paragliderFolder.subfolderName;
const resu = await uploadImage(pathImage, folderCoulidinary);
UrlsImage.name = paragliderFolder.subfolderName
UrlsImage[`photo_${index}`] = resu;
return UrlsImage
}, {})
console.log(imagesUrl);
})
The array exemple :
[
{
subfolderName: 'Arcus_Rs',
ImagesParagilder: [
'swing_arcus2rs_slider_arcus6.jpg',
'swing_arcus2rs_slider_arcus7.jpg',
'swing_arcus2rs_slider_arcuslim4.jpg',
'swing_arcus2rs_slider_arcuslime9.jpg'
],
color: [
'swing_arcus2rs_flame.png',
'swing_arcus2rs_lime.png',
'swing_arcus2rs_nightshade.png',
'swing_arcus2rs_ocean.png'
]
},
{
subfolderName: 'Coden_Pro',
ImagesParagilder: [ 'DSC5495.jpg' ],
color: [ 'Air.png', 'Earth.png', 'Fire.png', 'Water.png' ]
},
{
subfolderName: 'Tonic_2',
ImagesParagilder: [
'DSC5349r.jpg',
'DSC6647r.jpg',
'P1044262r.jpg',
'P1044438r.jpg',
'P1044656r.jpg'
],
color: [ 'Lind.png', 'Mustard.png' ]
}
]
So i got this result :
{
name: 'Arcus_Rs',
photo_0: 'url********'
}
{
name: 'Coden_Pro',
photo_0: 'url********'
}
{
name: 'Tonic_2',
photo_0: 'url********'
}
i got only one photo, i should have more photo, so for me it the await of the reduce who doesn't work.
If i try const imagesUrl = await Promies.all(paragliderFolder.ImagesParagilder.reduce( ect...)
i have a error: TypeError: object is not iterable (cannot read property Symbol(Symbol.iterator))
i don't understand why the console log doesn't wait the end of the reduce.
When you do await in your reducer, the function immediately returns a Promise and quits. At the second iteration, UrlsImage is actually the Promise from the first iteration, not the object you put into the reducer. I think this is not what you want.
Try something like this:
const promises = paragliderFolder.ImagesParagilder.map( async (image, index) => {
const pathImage = pathfolder + '/' + image;
const folderCoulidinary = 'paraglider/' + paragliderFolder.subfolderName;
const resu = await uploadImage(pathImage, folderCoulidinary);
return [`photo_${index}`, resu]
})
const entries = await Promise.all(promises)
const imagesUrl = Object.fromEntries(entries)
This will load each image and give you a tuple from which you can build what I assume you want imagesUrl to look like. Not sure about the name property, it seems like you wanted to override it in every iteration.
Here is a dummy-application using it:
function uploadImage(pathImage, folderCoulidinary){
return new Promise((resolve) => {
resolve('uploaded to:' + pathImage)
})
}
const paragliderFolder = {
ImagesParagilder: [
'img1', 'img2'
]
}
const pathfolder = 'pathfolder'
async function runit(){
const promises = paragliderFolder.ImagesParagilder.map( async (image, index) => {
const pathImage = pathfolder + '/' + image;
const folderCoulidinary = 'paraglider/' + paragliderFolder.subfolderName;
const resu = await uploadImage(pathImage, folderCoulidinary);
return [`photo_${index}`, resu]
})
const entries = await Promise.all(promises)
return Object.fromEntries(entries)
}
runit().then(o => console.log(o))
Hello after many research and trials here what i find.
the array.reduce() send only one promise so we can't use the Promise.all().
Here the trick to use a reduce with array of promise:
const array = [4, 7, 78];
const postsPromise = array.reduce((acc, element, index) => {
return acc.then(async result => {
const post = await fetch(`https://jsonplaceholder.typicode.com/posts/${element}`).then(res => res.json());
return { ...result, [`Id_${index}`]: post.title };
});
}, Promise.resolve({}));
const posts = await postsPromise;
console.log(posts);
The Promise.resolve send a Promise already resolved and we loop over it

Pushing data from socket.io into array

I have the following problem that I cant seem to work out. I'm trying to get an array of arrays from my socket.io emitter that is structured like follows:
[ [{...},{...},{...}] , [{...},{...}] , [{...}] ]
Instead I get this:
I need all the arrays in one master array so that I can render bootstrap cards for each sub array.
Client side code:
const socket = io("http://localhost:5000");
socket.on('data', (dta) => {
handleData(dta.data);
})
function handleData(data) {
const masterArray= [];
masterArray.push(data);
console.log(masterArray);
}
Server side code:
for(let i = 0 ; i < alarmpanels.length ; i++) {
const ElkClient = elkClient.ElkClient;
let client = new ElkClient({
connection: {
name: alarmpanels[i].name,
host: alarmpanels[i].host,
port: alarmpanels[i].port,
secure: alarmpanels[i].secure,
zones: alarmpanels[i].zones
}
});
connectClient(client);
}
async function connectClient(client) {
await client.connect();
const zonesArray = client.options.connection.zones;
const arr = [];
try {
const clhost = client.options.connection.host;
const clport = client.options.connection.port;
const clsecure = client.options.connection.secure;
let data = await client.getArmingStatus();
for (i = 0 ; i < zonesArray.length ; i ++) {
const armUpState = await data.areas[i].armUpState;
const clName = client.options.connection.name;
const zoneName = zonesArray[i].name;
const siteName = zonesArray[i].site;
const clzone = zonesArray[i].zone;
const totalPanels = zonesArray[i].length;
const info = new PanelStatus(clhost, clport ,clsecure, clzone, siteName, clName, zoneName, armUpState, totalPanels);
arr.push(info);
}
io.on('connection', (socket, req) => {
socket.emit('data', {data: arr});
})
}
catch (err) {
console.log("Connection Lost!");
}
}
Your client code need slight changes
1) keep the masterArray declaration outside of handleData
2) When pushing to masterArray, use ... spread operator.
const masterArray = [];
/*
const socket = io("http://localhost:5000");
socket.on("data", dta => {
handleData(dta.data);
});
*/
function handleData(data) {
masterArray.push(...data);
}
handleData([{ a: 4 }, { b: 5 }]);
handleData([{ z: 4 }]);
handleData([{ p: 4 }, { q: 5 }, { r: 5 }]);
console.log(masterArray);
If you can use es2020, you can use Array.flat()
let startingArray = [
[{
entry1: 1,
entry2: 2,
entry3: 3
}, {
entry4: 4,
entry5: 5,
entry6: 6
}, {
entry7: 7,
entry8: 8,
entry9: 9
}],
[{
entry10: 10,
entry11: 11,
entry12: 12
}, {
entry13: 13,
entry14: 14,
entry15: 15
}],
[{
entry16: 16,
entry17: 17,
entry18: 18
}]
]
const flattened = startingArray.flat()
console.log('startingArray', startingArray)
console.log('flattened', flattened)

Create a time counter to insert in array

I have a problem with creating a time in milliseconds to add to an array.
I have an app that reads from two external devices. So I need to insert a time about when these data are read.
I have a function like this:
async setupNotifications1(device) {
const service = this.serviceGeneral();
device.monitorCharacteristicForService(
service,
this.AccGyrMg,
(error, characteristic) => {
if (error) {
this.error(error.message);
return;
}
const buf = Buffer.from(characteristic.value, "base64");
const [...acc_dx] = [2, 4, 6].map(index => buf.readInt16LE(index));
this.setState(state => ({
acc_dx,
array_acc_dx: [
...state.array_acc_dx,
[acc_dx],
]
}));
/* pressure */
device.monitorCharacteristicForService(
service,
this.Pressure,
(error, characteristic) => {
if (error) {
this.error(error.message);
return;
}
const buf = Buffer.from(characteristic.value, "base64");
const [...pressure_dx] = [0, 2, 4, 6, 8].map(index => buf.readUInt16LE(index));
this.setState(state => ({
pressure_dx,
array_pressure_dx: [
...state.array_pressure_dx,
[pressure_dx]
]
}));
}
);
How can I create a time to concatenate at [...acc_dx] and [pressure_dx] ? Thank you
You could look into using setTimeout() for doing this.
https://facebook.github.io/react-native/docs/timers
So in your case you could have something like:
setTimeout(() => {
addToArray();
}, 300);

Unhandled Promise Rejection in React

I have an app that read data from external devices. These data are like acceleration, gyroscopic, magnetometer and pressure.
I'm trying to read the data in this way:
async setupNotifications2(device) {
let i = 0
const service = this.serviceGeneral();
while(i<10 ) {
i++
const promises = [
device.readCharacteristicForService(service, this.AccGyrMg),
device.readCharacteristicForService(service, this.Pressure)
]
Promise.all(promises)
.then((values) => {
// const time = new Date().getMilliseconds()
const time = bufAccGyrMg.readInt16LE(0);
const [...acc_sx] = [0,2, 4, 6].map(index => bufAccGyrMg.readInt16LE(index));
this.setState(state => ({acc_sx,array_acc_sx: [...state.array_acc_sx,[time , acc_sx]]}));
const [...gyr_sx] = [8, 10, 12].map(index => bufAccGyrMg.readInt16LE(index));
this.setState(state => ({gyr_sx,array_gyr_sx: [...state.array_gyr_sx,[time , gyr_sx]]}));
const [...mg_sx] = [14,16,18].map(index => bufAccGyrMg.readInt16LE(index));
this.setState(state => ({gyr_sx,array_mg_sx: [...state.array_mg_sx,[time , mg_sx]]}));
const bufPressure = Buffer.from(values[1].value, "base64");
const [...pressure_sx] = [0, 2, 4, 6, 8].map(index => bufPressure.readUInt16LE(index));
this.setState(state => ({pressure_sx,array_pressure_sx: [...state.array_pressure_sx,[time, pressure_sx]]}));
})
}
}
Now I have insert a condition inside a while only to try the code.
When I start the app, I receive this error:
YellowBox.js:67 Possible Unhandled Promise Rejection (id: 0):
BleError: Operation was rejected
How can I do, in your opinion?? Thank you.
I refactored your code a bit to help get rid of the Unhandled Promise Rejection error and help you point down the issue:
async setupNotifications2(device) {
//first of all, put everything inside a big try/catch block
try {
let i = 0
const service = this.serviceGeneral();
while(i<10 ) {
i++
const promises = [
// then make sure every promise passed to Promise.all() catches its own errors
device.readCharacteristicForService(service, this.AccGyrMg).catch( e => console.log(`err in first readCharacteristicForService `, e)),
device.readCharacteristicForService(service, this.Pressure).catch( e => console.log(`err in second readCharacteristicForService `, e))
]
// giben you're in an async function, you can do this to simplify a bit your code:
const values = await Promise.all(promises);
// const time = new Date().getMilliseconds()
const time = bufAccGyrMg.readInt16LE(0);
// this is an array, no need to overcomplicate with destructuring assignment... you can do the same below
const acc_sx = [0,2, 4, 6].map(index => bufAccGyrMg.readInt16LE(index));
this.setState(state => ({acc_sx,array_acc_sx: [...state.array_acc_sx,[time , acc_sx]]}));
const [...gyr_sx] = [8, 10, 12].map(index => bufAccGyrMg.readInt16LE(index));
this.setState(state => ({gyr_sx,array_gyr_sx: [...state.array_gyr_sx,[time , gyr_sx]]}));
const [...mg_sx] = [14,16,18].map(index => bufAccGyrMg.readInt16LE(index));
this.setState(state => ({gyr_sx,array_mg_sx: [...state.array_mg_sx,[time , mg_sx]]}));
const bufPressure = Buffer.from(values[1].value, "base64");
const [...pressure_sx] = [0, 2, 4, 6, 8].map(index => bufPressure.readUInt16LE(index));
this.setState(state => ({pressure_sx,array_pressure_sx: [...state.array_pressure_sx,[time, pressure_sx]]}));
} catch (err){
console.error(`general error: `, err)
}
}

Redeclare variables defined in global scope with a loop

I am trying to redeclare variables defined in global scope. I'd like to wrap each function
const {values, map, each} = require('lodash')
const wrapFnInLog = (fn) => (...input) => {
console.log({name: fn.name, input})
const possiblePromise = fn.apply(null, input)
if (get(possiblePromise, 'then')) {
return possiblePromise.then(output => {
console.log({name: fn.name, output})
return output
})
} else {
console.log({name: fn.name, output: possiblePromise})
return possiblePromise
}
}
let a = (arr) => map(arr, i => i.name)
let b = (obj) => a(values(obj))
const provide = [a, b]
provide.forEach(fn => wrapFnInLog(fn))
const example = {
personTom: {
name: 'Tom'
},
personJerry: {
name: 'Jerry'
}
}
b(example)
I'd like the output to look like this:
{ name: 'b', input: [ { personTom: [Object], personJerry: [Object] } ] }
{ name: 'a', input: [ [ [Object], [Object] ] ] }
{ name: 'a', output: [ 'Tom', 'Jerry' ] }
{ name: 'b', output: [ 'Tom', 'Jerry' ] }
The only way I've been able to achieve this is without a loop and it's via deliberately overwriting each variable one by one.
a = wrapFnInLog(a)
b = wrapFnInLog(b)
I'm wondering if it's possible to loop over [a, b] and overwrite the function definition, while keeping them in global module scope.
as already commented, you can use a destructuring assignment to assign multiple variables at once
let a = (arr) => map(arr, i => i.name);
let b = (obj) => a(values(obj));
[a,b] = [a,b].map(wrapFnInLog);
but unlike a destructuring assignment in combination with a variable declaration (let [a,b] = ...) you have to be careful what you write before this assignment and that you properly seperate commands.
Because with automatic semicolon insertation or, JS not inserting a semicolon where one should be,
let a = (arr) => map(arr, i => i.name)
let b = (obj) => a(values(obj))
[a,b] = [a,b].map(wrapFnInLog)
will be interpreted as
let a = (arr) => map(arr, i => i.name);
let b = (obj) => {
return a(values(obj))[a,b] = [a,b].map(wrapFnInLog);
}
//or in other words
let b = (obj) => {
let tmp1 = a(values(obj));
a; //the `a,` in something[a,b];
let tmp2 = [a,b].map(wrapFnInLog);
tmp1[b] = tmp2;
return tmp2;
}

Categories

Resources