I am trying to figure out how implement an asynchronous way of executing after an iteration is done
Essentially I have something like this:
data.businesses.map( club => {
axios.get(`${ROOT_URL}/club/${currentEmail}/${club.id}`)
.then( ({data}) => {
placeholder.push( Object.assign({}, club, data))
})
.then( () => ..do extra stuff )
})
// when data.businesses.map is done iterating all of the elements, do something
As you can see, there is some asynchronous issue when retrieving fetching information via axios
I'm still new to the idea of Promises but I am not sure where to apply, if it applicable
You can wait for a collection of promises to resolve (or at least one to reject) using Promise.all().
To gather that collection, you can have the iterator function for .map() return each Promise by removing the braces from the arrow function.
let promisedClubs = data.businesses.map( club =>
axios.get(`${ROOT_URL}/club/${currentEmail}/${club.id}`)
.then( ({data}) => {
placeholder.push( Object.assign({}, club, data))
})
.then( () => ..do extra stuff )
);
Promise.all(promisedClubs).then(clubs => {
// when data.businesses.map is done iterating all of the elements, do something
}, failedClub => {
// error handling
});
Related
So Promises make everything so much cleaner and all but how do I extrapolate the end of a promise chain to an external function?
For example:
SomePromise.then(result => {
// do stuff here
}).catch(err => {
// do more stuff here
})
SomePromise2.then(result => {
// do stuff here
}).catch(err => {
// do more stuff here
})
SomePromise3.then(result => {
// do stuff here
}).catch(err => {
// do more stuff here
})
Somehow, I'd like to extract the then and catch so that I can pass a function into the then like such:
SomePromise.then(handler);
SomePromise2.then(handler);
SomePromise3.then(handler);
I suppose I could extract the entire Promise into a function and pass parameters to and from there but that feels a bit clunky. Any elegant solutions?
Edit: I should elaborate that I'm focusing on the catch part. Imagine multiple different queries with identical data-parsers and error handlers.
Edit 2: In hindsight I realize now that I could just extrapolate
handler = () => {}
errorHandler = () => {}
Promise.then(handler)
.catch(errorHandler)
but the
let p1=Promise.resolve(1);
function handler(data){console.log(data)};
function errorHandler(error){console.log(error)};
p1.then(handler,errorHandler);
Multiple things here, your code you are expecting is:
SomePromise.then(handler);
However it should be in actual as
function handler(data){};
SomePromise.then((data)=>handler(data));
So getAstronautsData make request to API then return array of promises. This promises mast make request to Wiki API and parse response in object. Then exampleAsyncFunc must wait all promises and return one big object with all info about Astronauts.
But if I use Promise.all function ending and console is clear.
function getAstronautsData() {
return new Promise((resolve, reject) => {
getData('http://api.open-notify.org/astros.json', "http", (data) => {
resolve(data) // get Astronauts list from API
})
}).then((astronautsList) => {
return astronautsList.people.map((person => // return array of promises
new Promise(resolve => {
getWikiData(person.name, (data) => { // request on Wiki API
resolve({info: data.extract, img: data.thumbnail.source})
})
})
))
})
}
async function exampleAsyncFunc (){
let promisesList = await getAstronautsData()
// next code just few variant was i try
let data = await Promise.all(promisesList)// it's not working.
console.log(data)
Promise.all(promisesList).then(data => console.log(data)) //it's not working. Function display nothing
promisesList.forEach((promise) => { //it's working but not so elegant
promise.then(data => console.log(data))
})
}
exampleAsyncFunc ()
function getWikiData(searhTerm, callback) {
getData(getUrlString(searhTerm), "https", (data) => {
const regex = new RegExp(searhTerm.replaceAll(" ", ".*"));
for (let page in data.query.pages) {
if (data.query.pages[page].title === searhTerm || regex.test(data.query.pages[page].title)) {
callback(data.query.pages[page])
return
}else{
callback(null)
}
}
})
}
You appear to be using Promise.all correctly, but if any of the Promises in Promise.all rejects, then overall Promise.all promise will reject too and nothing will happen, where in your forEach version it'll simply skip those promises silently and move on to the next entries.
Likewise if any of the promises in the list stays pending: if so then the Promise.all promise will never resolve. This could be because you have a long list of return values and the whole list takes a longer-than-expected time to resolve, or because your getWikiData call encounters an error and you don't pass that out to reject that particular promise in your array.
You can debug this behavior by ensuring that each of your calls to then is followed by .catch(console.error) (or some more robust error handler).
Let me first disclose that I am a big promise partisan and frankly deplore callbacks. The implication here is that I would not have written your getData and getWikiData with callbacks.
I will also point out that I second what #t.niese said in the comments: Because it does not make sense having both let data = await Promise.all(promisesList) and promisesList.forEach((promise) => {.
Anyway, your code is unnecessarily complex and can be simplified like so:
function getAstronautsData(callback) {
getData('http://api.open-notify.org/astros.json', "http", data => {
callback(data.people.map(person =>
new Promise(resolve => {
getWikiData(person.name, data => {
resolve(data);
})
}))
)
})
}
function exampleAsyncFunc (){
getAstronautsData(promises => {
Promise.all(promises)
.then(result => {
//result will contain those resolved promises
console.log(result);
})
});
}
exampleAsyncFunc ()
Notice that I am passing a callback to getAstronautsData and call it from inside that function with the array of promises you ultimately want to resolve. No need for async here either as you can see.
Ok, problem was in API (in API one of astronauts have name "Tom Marshburn" but on Wiki his page have title "Thomas Marshburn") and function getWikiData not return any data on error. So i fixed this problem.
Thanks you all for you help!!!
I'm currently a little stuck with a race condition, and I'm pulling my hair out. Essentially, I'm querying an API, adding the results to the DB, then doing stuff with the returned/saved data.
I'm less asking about this specific problem, and more the design pattern of how to fix this sort of issue. The line p.push(formatter.createAccounts(account, id)); might run upto 1000 times, and might take 20 seconds or so.
Any ideas on what I'm doing wrong will be super helpful
Thanks,
Ollie
// get all users
Promise.all(updatedUsers)
.then(() => {
// create an array of promises
const p = [];
users.then((user) => {
...
ids.forEach((id) => {
const accounts = foo.getAccounts(id, true); // returns a promise
accounts.then((account) => {
// add the return data from here to the promise array at the bottom
p.push(formatter.createAccounts(account, id));
});
});
});
// this would ideally be a promise array of the data from above - but instead it just evaluates to [] (what it was set to).
Promise.all(p).then(() => {
// do my stuff that relies on the data here
})
});
The problem is that you are not including the foo.getAccounts promise into your promises array. Modified version:
Promise.all(updatedUsers)
.then(() => {
users.then((user) => {
return Promise.all(ids.map(id => {
//for each ID we return a promise
return foo.getAccounts(id, true).then(account => {
//keep returning a promise
return formatter.createAccounts(account, id)
})
}).then((results) => {
//results is an array of the resolved values from formatter.createAccounts
// do my stuff that relies on the data here
})
})
//rest of closing brackets
I am trying to get a nested Promise.all * map logic to work. I keep getting undefined values when I reach getData2.
exports.getData = (param1, param2) => {
return getData0(param1, param2)
.then(data0 => Promise.all(data0.map(e => getData1(e.id))))
.then(data1 => Promise.all(data1.map(i => getData2(i.id))))
.then(data2 => data2.map(a => getData3(a.id)))
.catch(err => console.error(err.message));
};
P.S:
1. getData0 to getData1 return structures (e.g. { a: val1, b: val2 })
2. I assume the problem is in the way getData's are written. I suspect they should return promises. Can anyone give me a dummy example about a function that returns a structure wherein both elements of the (see a and b above) are obtained in an async way?
Thanks!
Firstly, all your getData* methods should return Promise objects if they are doing any asynchronous operations. (for e.g fetching data).
getData3 could be an exception to this since it doesn't look like anything needs to be done after all getData3 calls are completed.
If that's not the case you could use similar method for getData3 as for above.
e.g data2 => Promise.all(data2.map(a => getData3(a.id)))
Now let's look over the code line by line
exports.getData = (param1, param2) => {
return getData0(param1, param2)
.then(data0 => Promise.all(data0.map(e => getData1(e.id))))
// data0 here should be an Array
.then(data1 => Promise.all(data1.map(i => getData2(i.id))))
// 1. data1 will be an Array with results from each getData1 call
// mapped by index. for e.g [result1, result2, ...]
// 2. depending on the type of result (Object/Array) from getData1
// you should extract `id` from `i`(result item) and then
// trigger `getData2`using that `id`. I'm assuming the issue is
// here.
.then(data2 => data2.map(a => getData3(a.id)))
.catch(err => console.error(err.message));
};
As for Can anyone give me a dummy example about a function that returns a structure wherein both elements of the (see a and b above) are obtained in an async way?
I believe this should answer that How to use Promise.all with an object as input
I've marked Dhruv's answer as the valid one as it explains different concepts involved in the logic I described.
getData1 and getData2 indeed make calls to an async function (in my case: doSomeEWSwork below) while getData3 is sync.
function doSomeEWSwork(param) {
var ewsFunction = '.....';
var ewsArgs = ....;
return ews.run(ewsFunction, ewsArgs)
.then(result => result.something)
.catch(err => console.log(err.message));
}
My old getData1 and getData2 used to return structure objects (e.g. {a: val1, b: val2} instead of promises (needed for Promise.all). That was causing the async blocs to never execute/evaluate.
My new getData1 and getData2 have the following skeleton:
function getData1_or_2(param) {
var promise = new Promise(function(resolve, reject) {
doSomeEWSwork(param) // <- important: settle this using 'then'
.then(res => {
var ret = { a: res.val1, b: res.val2 };
resolve(ret);
}).catch(err => {
console.log(err.message);
reject(Error(err.message));
});
});
return promise;
}
So the main method (already included in my original question) works fine now since I am returning promises for async and object for sync.
exports.getData = (param1, param2) => {
return getData0(param1, param2)
.then(data0 => Promise.all(data0.map(e => getData1(e.id))))
.then(data1 => Promise.all(data1.map(i => getData2(i.id))))
.then(data2 => data2.map(a => getData3(a.id)))
.catch(err => console.error(err.message));
};
It's just the combination of nested promises, map and structure objects that confused me.
Thanks!
I've seen questions similar to this in various forms but I can't seem to crack this particular case...
I want to pass a function inside a Promise, then execute that Promise and do something with the result. The function to be passed in is the database transaction txn function below:
db.transaction(txn => {
//lodash reduce function to execute each promise sequentially
_.reduce(promisesToExecute, (pending, next, i) => {
return next
//this bit is wrong but I don't know how else to pass in txn
//how should I pass txn into the promise?
.then(fn => fn(txn))
.then(newIds => {
if (newIds) {
returnContent[i] = newIds
}
return next
})
}, Promise.resolve())
})
And the promise I want to execute is here
(newVals, id, fkId) => {
return new Promise((resolve, reject) => {
return txn => {
//I want txn to be available in here to use
return db.table('Users')
.insert(newVals)
.transacting(txn)
.then(res => {
resolve(res.id)
})
.catch(err => {
reject(err)
})
}
})
Any ideas? Do I need to somehow pass the newIds => {} function in as a callback?
The problem here is that you're creating promises that will never resolve. They have a function inside them that never gets called, and the resolve and reject hang off of that function.
So fix your second chunk of code to return functions, not promises:
(newVals, id, fkId) =>
txn =>
db.table('Users')
.insert(newVals)
.transacting(txn)
.then(res => res.id)
Then fix the first chunk of code accordingly:
db.transaction(txn =>
_.reduce(functions, (pending, next, i) =>
next(txn)
.then(newIds => {
if (newIds) {
returnContent[i] = newIds
}
})
, Promise.resolve())
);