Nested, asynchronous promises: how to know when they're all resolved? - javascript

I'm doing some api calls from a web app. First I'm getting my partner details (a group of hotels). This partner has some properties. For each of these properties, I need to get their rooms; then for each of these rooms, I need to get their availabilities and bookings.
I would like to perform all these calls asynchronously (ie as soon as I have the room info of a property, I can get their bookings and availabilities, without waiting to have the details of all the properties).
I would like to know when everything is loaded. Below is a simplified code using timeouts to simulate api calls.
I've tried to push each new promise in an array, and use Promise.all on this array, but I run into the same issue.
I've also tried to increment a counter everytime a query is made, and decrease the counter everytime one is resolved; this is working but doesn't seem very clean?
const getPartner = () => {
const p = new Promise((resolve) => {
setTimeout(() => {
console.log('getPartner finished');
resolve({
properties: [1, 2, 3],
});
}, 1000);
});
return p;
}
const getRooms = () => {
const p = new Promise((resolve) => {
setTimeout(() => {
console.log('getRooms finished');
resolve([1, 2, 3]);
}, 1000);
});
return p;
}
const getAvailabilities = () => {
const p = new Promise((resolve) => {
setTimeout(() => {
console.log('getAvailabilities finished');
resolve([1, 2, 3]);
}, 1000);
});
return p;
}
const getBookings = () => {
const p = new Promise((resolve) => {
setTimeout(() => {
console.log('getBookings finished');
resolve([1, 2, 3]);
}, 1000);
});
return p;
}
function main() {
getPartner().then((partner) => {
Promise.all([
partner.properties.forEach((property) => {
getRooms().then((rooms) => {
rooms.forEach((room) => {
getAvailabilities();
getBookings();
})
});
})
]).then(() => {
console.log('all finished');
});
});
}
main();
I expect "all finished" to show last in the console. Instead it shows immediately after "getPartner finished"
Edit: here's what I tried with a counter:
const promises = [];
const getPartner = () => {
const p = new Promise((resolve) => {
setTimeout(() => {
console.log('getPartner finished');
resolve({
properties: [1, 2, 3],
});
}, 1000);
});
promises.push(p);
return p;
}
const getRooms = () => {
const p = new Promise((resolve) => {
setTimeout(() => {
console.log('getRooms finished');
resolve([1, 2, 3]);
}, 1000);
});
promises.push(p);
return p;
}
const getAvailabilities = () => {
const p = new Promise((resolve) => {
setTimeout(() => {
console.log('getAvailabilities finished');
resolve([1, 2, 3]);
}, 1000);
});
promises.push(p);
return p;
}
const getBookings = () => {
const p = new Promise((resolve) => {
setTimeout(() => {
console.log('getBookings finished');
resolve([1, 2, 3]);
}, 1000);
});
promises.push(p);
return p;
}
function main() {
getPartner().then((partner) => {
partner.properties.map((property) => {
getRooms().then((rooms) => {
getAvailabilities();
getBookings();
})
})
})
.then(() => {
Promise.all(promises).then(() => {
console.log('all finished');
});
});
}
main();

Several issues:
forEach does not return anything, yet you should return an array -- to feed to Promise.all.
Use Promise.all in every case where you have multiple promises
Return the promises whenever you need to chain one in a then callback
Here is how it would work:
function main() {
getPartner().then((partner) => {
return Promise.all(partner.properties.map((property) => {
return getRooms().then((rooms) => {
return Promise.all(rooms.map((room) => {
return Promise.all([getAvailabilities(), getBookings()]);
}))
});
})).then(() => {
console.log('all finished');
});
});
}

Related

Function calls from data.map are not executed synchronously

I am trying to call same function from map. Depending upon number of entries function will be called.
Here is my code
getDetails = (inputData) => {
const data = {
accountName: "test",
};
let url = `/rest/jarvis/reports/v1/getData`;
let thisdata = this;
post(url, data)
.then((response) => {
if (
response.data &&
response.data.sb &&
response.data.sb.length > 0
) {
this.props.bs.betDetails = {
data: [...response.data.sportsbookBets],
exportAccesses: response.data.exportAccesses,
};
this.props.bs.betDetails.data.map((value, index) => {
console.log("Index Value --->", index);
this.getEventDetails(value, index);
}),
this.updateReducer({
...this.props.bs,
});
}
}
}
getEventDetails = async (value, index) => {
let _this = this;
let myPromise = new Promise(function (myResolve, myReject) {
console.log("Index Value at the entry--->", index);
const data = {
accountName: "test",
};
let url = `/rest/jarvis/reports/v1/getBetEventData`;
post(url, data).then((response) => {
if (response.data) {
console.log("Index Value inside--->", index);
_this.eventIdIndexMap.set(
list.eventId != null
? list.eventId
: _this.props.bs.betEventRequest.EventId,
index
);
_this.props.bs.testEventDetails[
_this.eventIdIndexMap.get(
list.eventId != null
? list.eventId
: _this.props.bs.betEventRequest.EventId
)
] = _this.props.bs.betEventResponse;
}}}
await myPromise;
}
Here problem I am facing is inside post call of 'getEventDetails' index values are not getting in sequence.
I have used promise await and async but it is not working.
Can someone please tell me how to get it properly ?
map calls its callback function without await. You can use asynchronous functions in the callback function but they won't be executed in order.
But you can create your own map with await:
async function map(array, cb) {
const result = [];
for (const element of array) {
result.push(await cb(element));
}
return result;
}
async function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
(async () => {
console.log(await map([3, 2, 1], async el => { await sleep(el * 100); return 2 * el; }));
})();
or create your own Array.prototype.syncMap:
Array.prototype.syncMap = async function(cb) {
const result = [];
for (const element of this) {
result.push(await cb(element));
}
return result;
}
async function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
(async () => {
console.log(await [3, 2, 1].syncMap(async el => { await sleep(el * 100); return 2 * el; }));
})();
It looks like you don't even need a result. You can write your own Array.prototype.syncForEach:
Array.prototype.syncForEach = async function(cb) {
for (const element of this) {
await cb(element);
}
}
async function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
[3, 2, 1].syncForEach(async el => { await sleep(el * 100); console.log(2 * el); });
[30, 20, 10].forEach(async el => { await sleep(el * 10); console.log(2 * el); });
As you can see in the output the functions in syncForEach are executed in order and the functions in forEach are executed out of order.

Unable to syncronize the functions

I'm trying to execute the functions one by one in synchronization.
var subtasks = ['Site', 'Draw', 'Material', 'Conduction', 'Cable', 'Install', 'Foundation']
function clickMe() {
return new Promise(resolve => {
jQuery("#addtoptasksubmit").trigger("click"); // triggering the button click
resolve("done click");
});
}
function typeWord(word) {
return new Promise(resolve => {
jQuery("#todotask").val(word); // input
resolve("done type");
});
}
function createSubTask() {
return new Promise(res => {
jQuery('#todotask').focus();
res("done")
})
};
function startLoop(i) {
new Promise(resolve => {
var promise = createSubTask();
promise.then(resolve => {
var typePromise = typeWord(subtasks[i]);
typePromise.then((resolve) => {
var clickPromise = clickMe();
clickPromise.then((resolve) => {
console.log(resolve);
});
});
});
})
}
let i = 0;
let prom = startLoop(i);
prom.then((res) => {
startLoop(i++);
})
code is not working properly and also I wanted to increment i automatically. For loop is a no show.
I've tried for loop and recursive async function on chrome.
Doesn't your startLoop(i) function need a return statement for the new Promise() call? I have to imagine this:
// prom = undefined as startLoop never returns anything
let prom = startLoop(i);
Change your code like so:
// Current code from description
function startLoop(i) {
new Promise(resolve => {
// Fixed code
function startLoop(i) {
return new Promise(resolve => {
var subtasks = ['Site', 'Draw', 'Material', 'Conduction', 'Cable', 'Install', 'Foundation'];
function clickMe() {
return new Promise(resolve => {
setTimeout(() => {
jQuery("#addtoptasksubmit").trigger("click");
resolve("done click");
}, 1000);
});
}
function typeWord(word) {
return new Promise(resolve => {
jQuery("#todotask").val(word);
resolve("done type");
});
}
function createSubTask() {
return new Promise(res => {
jQuery('#todotask').focus();
res("done");
})
};
function startLoop(i) {
return new Promise(res => {
var promise = createSubTask();
promise.then(
(resolve) => {
var typePromise = typeWord(subtasks[i]);
typePromise.then((resolve) => {
console.trace(resolve);
var clickPromise = clickMe();
clickPromise.then((resolve) => {
console.trace(resolve);
res("done loop " + subtasks[i]);
});
});
}
);
})
}
var _index_ = 0;
var _how_ = setInterval(() => {
if (_index_ < subtasks.length) {
let loopPromise = startLoop(_index_);
loopPromise.then((resolve) => {
_index_ += 1;
});
} else {
console.log("all done go get the new task");
clearInterval(_how_);
}
}, 10000);
I can optimise it further....

Collect promises generated within promises in forEach

I want to display the progress of a migration operation to mongodb.
The script looks like:
let promises = [];
mylist.forEach(idx => {
myCollection.find({id: idx}).toArray().then(msgs => {
promises.push(myCollection2.insertMany(msgs.map(msg => ({
msg: msg,
otherFields: others
}))))
})
});
// function to display the progress:
allProgress(promises,
(p) => {
console.log(`% Done = ${p.toFixed(2)}`);
});
function allProgress(proms, progress_cb) {
let d = 0;
progress_cb(0);
proms.forEach((p) => {
p.then(()=> {
d ++;
progress_cb( (d * 100) / proms.length );
});
});
return Promise.all(proms);
}
This won't work because promises is empty when allProgress() is called.
How can I correctly collect all promises before calling allProgress()?
UPDATE
In the process of producing a MCVE, I came up with
let promises = [];
[1,2,3].forEach(idx => {
test(1000).then(promises.push(test(10000)));
});
console.log(promises.length);
// function to display the progress:
allProgress(promises,
(p) => {
console.log(`% Done = ${p.toFixed(2)}`);
});
function test(ms) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`Waited ${ms}`);
resolve();
}, ms);
});
}
function allProgress(proms, progress_cb) {
let d = 0;
progress_cb(0);
proms.forEach((p) => {
p.then(() => {
d++;
progress_cb((d * 100) / proms.length);
});
});
return Promise.all(proms);
}
This script, to my surprise, works... Why isn't equivalent to my original script?
UPDATE2
[1,2,3].forEach(idx => {
test(1000).then(_ => {
promises.push(test(10000))
});
});
This should be the MCVE, which does not work.
The .find() function is async so while you are still in the process of finding elements the forEach loop itself moves on. In the end you end up waiting for your .find().
What you could do is inside of the .then() callback, check the index of the current forEach item, if you are at the last item then we know all promises have been returned. So call your allProgress function there.
This should allow enough time for waiting for everything to come together. Additionally, by checking against the index, we know that we will only call your allPromises function at completion. Not multiple times as each forEach loop occurs.
let promises = [];
mylist.forEach((idx, index) => {
myCollection.find({id: idx}).toArray().then(msgs => {
promises.push(myCollection2.insertMany(msgs.map(msg => ({
msg: msg,
otherFields: others
}))));
if((index + 1) === mylist.length){
// function to display the progress:
allProgress(promises, (p) => {
console.log(`% Done = ${p.toFixed(2)}`);
});
}
})
});
function allProgress(proms, progress_cb) {
let d = 0;
progress_cb(0);
proms.forEach((p) => {
p.then(()=> {
d ++;
progress_cb( (d * 100) / proms.length );
});
});
return Promise.all(proms);
}
Edit:
Your MCVE (latest edit) is failing for the exact same reason. Your requests are async which is allowing the loop to progress without waiting. Once again, check the index and call when all done.
let promises = [];
let entries = [1, 2, 3]
entries.forEach((idx, index) => {
test(1000).then(_ => {
promises.push(test(10000))
if((index + 1) === entries.length) {
console.log(promises.length);
// function to display the progress:
allProgress(promises,
(p) => {
console.log(`% Done = ${p.toFixed(2)}`);
});
}
});
});
function test(ms) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`Waited ${ms}`);
resolve();
}, ms);
});
}
function allProgress(proms, progress_cb) {
let d = 0;
progress_cb(0);
proms.forEach((p) => {
p.then(() => {
d++;
progress_cb((d * 100) / proms.length);
});
});
return Promise.all(proms);
}
myCollection.find({id: idx}) is async operation.
so you can like this:
let promises = [];
mylist.forEach(idx => {
myCollection.find({id: idx}).toArray().then(msgs => {
promises.push(myCollection2.insertMany(msgs.map(msg => ({
msg: msg,
otherFields: others
}))))
allProgress(promises,
(p) => {
console.log(`% Done = ${p.toFixed(2)}`);
});
})
});
function allProgress(proms, progress_cb) {
let d = 0;
progress_cb(0);
proms.forEach((p) => {
p.then(()=> {
d ++;
progress_cb( (d * 100) / proms.length );
});
});
return Promise.all(proms);
}

Return dependent promises sequentially inside loop

I'm working on shopify integration.
We receive an array items then loop through them and add them a new model (func1) then I need to use that result from the first and add it to a schedule (func2).
I need this functions to run sequentially because I'm adding the results to a schedule and if I have two results for the same date and they don't yet exist in the database if the they run in parallel it creates 2 separate entries in the database instead of one entry with the two values.
The way I need to return is func1, func2, func1, func2....
But at the moment is returning func1, func1...func2, func2...
This is a simplified example of what I need to accomplish.
const func2 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
return console.log('func2');
}, 3000);
});
};
const func1 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Func1');
func2();
}, 1000);
});
};
const array = [1, 2, 3, 4, 5];
const test = () => {
array.map(x => {
func1();
});
};
test();
If there is something that isn't clear please let me know.
Thanks
you can use async/await and for loop in order do create a synced like iteration. and use it again in your func1 in order to reslove
const func2 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('func2');
resolve();
}, 3000);
});
};
const func1 = () => {
return new Promise( (resolve, reject) => {
setTimeout(async () => {
console.log('Func1');
await func2();
resolve();
}, 1000);
});
};
const array = [1, 2, 3, 4, 5];
const test = async () => {
for(let i=0;i<array.length;i++){
await func1();
}
};
test();
This is the perfect place to use the traverse function:
const traverse = (xs, f) => xs.reduce((promise, x) =>
promise.then(ys => f(x).then(y => Promise.resolve(ys.concat([y])))),
Promise.resolve([]));
const times2 = x => new Promise(resolve => setTimeout(() => {
console.log("times2", x);
resolve(2 * x);
}, 1000));
const minus1 = x => new Promise(resolve => setTimeout(() => {
console.log("minus1", x);
resolve(x - 1);
}, 1000));
const xs = [1,2,3,4,5];
const ys = traverse(xs, x => times2(x).then(minus1));
ys.then(console.log); // [1,3,5,7,9]
Hope that helps.
const func2 = async (modalValue) => {
let result = modalValue*5;
console.log(`Function2 result: ${result}`)
return result;
};
async function getModalValue(i){
// rest of your Code
return { modal: i*2}
}
const func1 = async (item) => {
let {modal} = await getModalValue(item);
console.log(`Function1 modal: ${modal}`)
await func2(modal);
};
const array = [1, 2, 3, 4, 5];
const test = async () => {
for(let i=0;i<array.length;i++){
await func1(array[i]);
}
};
test().then((resp)=> {console.log("Completed")})
.catch((err)=>{console.log("failure")})

mergePromise(...).then is not a function

When I run the code, I get the error
mergePromise(...).then is not a function.
I want to know why I got this error.
const timeout = ms => new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, ms);
});
const ajax1 = () => timeout(2000).then(() => {
console.log('1');
return 1;
});
const ajax2 = () => timeout(1000).then(() => {
console.log('2');
return 2;
});
const ajax3 = () => timeout(2000).then(() => {
console.log('3');
return 3;
});
const mergePromise = ajaxArray => {
const data=[];
ajaxArray[0]().then(i=>data.push(i));
timeout(1005).then(() => {
ajaxArray[1]().then(i=>data.push(i));
});
timeout(10).then(() => {
ajaxArray[2]().then(i=>data.push(i));
});
return data;
};
I guess maybe the timeout function has some mistake. What am I doing wrong?
Maybe you can try return Promise.resolve(data) instead, it would return a promise, which is what you need.

Categories