I've searched a lot, tried many things but I can't come to a clean conclusion. I'm chaining a lot of Promises:
getInstalledComponents().call().then(response => {
return {
'installed_components': {
'data': response
}
};
}).then((passed_data) => {
return getDemosCategories().call().then(response => {
return {...passed_data, ...{'demos_categories': response}};
});
}).then((passed_data) => {
return getDemosData().call().then(response => {
return {...passed_data, ...{'demos_data': response}};
});
}).then((passed_data) => {
});
I can't handle errors with this. I'd like to re-write this in such a manner that if one of them fails, all of them should fail and return.
I tried to asy/await each promise function, reached nothing. The promises that return the data I need are getInstalledComponents, getDemosCategories, getDemosData and each of them is a Promise-based AJAX call. It's basically resolved when the AJAX call comes back.
This doesn't look clean, nor useful. How can I re-write this to fit my requirements?
Utilizing Promise.all we are able to parallelize requests:
Promise.all([
getInstalledComponents().call(),
getDemosCategories().call(),
getDemosData().call()
])
.then(([installedComponents, demosCategories, demosData]) => ({
"installed-categories": { data: installedComponents },
"demos-categories": { data: demosCategories },
"demos-data": {data: demosData}
}))
.catch(e => handleSomeErrorForAnyOfRequestsAbove(e))
Using async/await there still Promise.all will be needed:
const result = {};
try {
const [installedComponents, demosCategories, demosData] = await
Promise.all([
getInstalledComponents().call(),
getDemosCategories().call(),
getDemosData().call()
]);
result["installed-components"] = installedComponents;
result["demos-categories"] = demosCategories;
result["demos-data"] = demosData;
} catch(e) {
handleSomeErrorFromAnyOfRequestFailed();
}
If you were to simply put a catch block at the end of the last then it would catch errors in any of the functions.
Promise.reject("firstFailed")
.then(passedData => console.log("second") || passedData)
.then(passedData => console.log("third") || passedData)
.then(passedData => console.log("fourth") || passedData)
.catch(error => console.error(error));
As you can see from the lack of console logs in the example above, first rejection stops the execution of any other then block
Related
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!!!
The following function returns the Promise before this.codesService.getCostCodes() resolves, resulting in undefined
async getTopParentByChildId(id: string) {
let parent;
await this.codesService.getCostCodes().subscribe( data => {
parent = data.body[0];
});
return new Promise<BidItem>(resolve => { //returning `parent` results in the same issue
resolve(parent);
});
}
getTopParentByChildId() is being called by another asynchronous function that has the same issue where it returns undefined before resolving the async call:
async populateBidItemObjectArray(node){
const parent = await this.getTopParentByChildId(node.id); //should wait for function to return before executing the rest
const bidItem = {
name: parent.name,
id: parent.id
};
return new Promise<BidItem>(resolve => { //returns undefined before this.getTopParentByChildId is resolved
resolve(parent);
});
}
I've read a lot about async/await and promises, but none of the solutions I've tried have worked for me so far. I'm unable to understand why it's not waiting for the async functions to resolve when I'm using the async/await keywords.
You can await a Promise and that is it. You can't await a rxjs subscription (or even an rxjs observable which would have made more sense to try but still won't work).
You can refactor getTopParentByChildId to this which removes the async/await as they are not needed.
getTopParentByChildId(id: string) {
return this.codesService.getCostCodes()
.pipe(map(data => data.body[0]))
.toPromise();
}
You can refactor populateBidItemObjectArray to this.
populateBidItemObjectArray(node) {
return this.getTopParentByChildId(node.id)
.then(_ => {
return {
name: _.name,
id: _.id
};
});
}
I'm filling an array inside a loop and I need the full array when the loop finishes.
I've tried handling everything through promises or by using a counter but i can't seem to figure out the trick here.
lambda.listFunctions({}).promise()
.then((data) => {
data.Functions.forEach(func => {
lambda.listTags({ Resource: func.FunctionArn }).promise()
.then((data) => {
if ("Edge" in data.Tags) {
available_functions.push(func.FunctionName)
}
})
});
console.log(available_functions)
})
available_functions is always empty unless I console log it at the end of each foreach loop and then I have it returning 18 times which is not what I want.
I believe you can just promise it chain it to ensure all operations within the scope of the then completes before going down the chain.
lambda.listFunctions({}).promise()
.then(data => {
const { Functions } = data;
// I converted this from forEach to for of
for (const func of Functions) {
lambda.listTags({ Resource: func.FunctionArn }).promise()
.then(data => {
if ("Edge" in data.Tags) {
available_functions.push(func.FunctionName)
}
})
}
// also you can promise chain it if available_functions is within scope
})
.then(() => console.log(available_functions))
Or the cleaner async await way would look something like...
async fn() {
const available_functions = [];
const { Functions } = await lambda.listFunctions({}).promise();
for (const func of Functions) {
const tags = await lambda.listTags({ Resource: func.FunctionArn }).promise();
if ("Edge" in tags) {
available_functions.push(func.FunctionName)
}
}
return available_functions
}
Hope this helps!
You can use Promise.all with your problem. See documentation on Promise.all().
const available_functions = [];
lambda.listFunctions({}).promise()
.then((data) => {
const promises = []; // Collect promises
data.Functions.forEach(func => {
promises.push(lambda.listTags({ Resource: func.FunctionArn }).promise()
.then((data) => {
available_functions.push(func.FunctionName)
return Promise.resolve(available_functions);
})
);
});
Promise.all(promises)
.then(results => {
console.log(available_functions)
// or
console.log(results[results.length - 1]);
});
});
im really new to this of promises and im getting headaches trying to understand this, so now im trying to get an answer from a method that returns a promise, then i catch the value in a conditional and make some other operations
let addService = async(req, res) => {
checkCategoryExists(param).then(result => {
if(result){
// code here
}
}).catch(err => console.log(err));
}
let checkCategoryExists = async(param) => {
let docs = db.collection(collectionName).doc(param);
docs.get()
.then(categoryDoc => {
if(categoryDoc.exists){
if(categoryDoc.data().param== param){
return true;
}
} else {
return false;
}
})
.catch(err => false);
}
the method "checkCategoryExists" is a query to a firestore db. When i tried to check if result variable is true or false, it happens to be undefined. its not with ".then()" that i get to catch the value from the returned promise? if someone can help me, thanks in advance
So as mentioned above I think your issue is based on not returning the results of your document search so both examples below handle that.
I also noticed that you were using async tags on your functions but not ever using await so I wanted to give examples of both ways of doing it in case you wanted to use Async/Await but weren't certain how.
In the promise chain example I'm relying on the syntax of arrow functions to create the returns (no {} means return right side of equation) since none of your given code requires data manipulation before return (if your actual code needs that you should of course use brackets and remember your return statement :D )
If you choose to use Async/Await you can structure the code much more closely to synchronous examples and use try catch statements. Sometimes I find this syntax more clear if I have to do a lot of processing/manipulation before returning a result.
Good Luck :)!
// Promise Chains
const addService = (req, res) =>
checkCategoryExists(param)
.then(result => /* do stuff */)
.catch(err => console.error(err.message));
const checkCategoryExists = param =>
db.collection(collectionName.doc(param)
.get()
.then(categoryDoc =>
Promise.resolve((categoryDoc.exists && categoryDoc.data().param === param))
);
// OR using Async/Await
const addService async (req, res) => {
try {
const catExists = await checkCategoryExists(param);
if (!catExists) {
throw new Error('error code');
}
// Do Stuff.
} catch (error) {
console.error(error);
}
};
const checkCategoryExists = async param => {
try {
const docs = await db.collection(collectionName)
.doc(param)
.get();
return (docs.exists && docs.data().param === param);
} catch (error) {
console.error(error)
}
}
I have the following functions with promises:
const ajaxRequest = (url) => {
return new Promise(function(resolve, reject) {
axios.get(url)
.then((response) => {
//console.log(response);
resolve(response);
})
.catch((error) => {
//console.log(error);
reject();
});
});
}
const xmlParser = (xml) => {
let { data } = xml;
return new Promise(function(resolve, reject) {
let parser = new DOMParser();
let xmlDoc = parser.parseFromString(data,"text/xml");
if (xmlDoc.getElementsByTagName("AdTitle").length > 0) {
let string = xmlDoc.getElementsByTagName("AdTitle")[0].childNodes[0].nodeValue;
resolve(string);
} else {
reject();
}
});
}
I'm trying to apply those functions for each object in array of JSON:
const array = [{"id": 1, "url": "www.link1.com"}, {"id": 1, "url": "www.link2.com"}]
I came up with the following solution:
function example() {
_.forEach(array, function(value) {
ajaxRequest(value.url)
.then(response => {
xmlParser(response)
.catch(err => {
console.log(err);
});
});
}
}
I was wondering if this solution is acceptable regarding 2 things:
Is it a good practice to apply forEach() on promises in the following matter.
Are there any better ways to pass previous promise results as parameter in then() chain? (I'm passing response param).
You can use .reduce() to access previous Promise.
function example() {
return array.reduce((promise, value) =>
// `prev` is either initial `Promise` value or previous `Promise` value
promise.then(prev =>
ajaxRequest(value.url).then(response => xmlParser(response))
)
, Promise.resolve())
}
// though note, no value is passed to `reject()` at `Promise` constructor calls
example().catch(err => console.log(err));
Note, Promise constructor is not necessary at ajaxRequest function.
const ajaxRequest = (url) =>
axios.get(url)
.then((response) => {
//console.log(response);
return response;
})
.catch((error) => {
//console.log(error);
});
The only issue with the code you provided is that result from xmlParser is lost, forEach loop just iterates but does not store results. To keep results you will need to use Array.map which will get Promise as a result, and then Promise.all to wait and get all results into array.
I suggest to use async/await from ES2017 which simplifies dealing with promises. Since provided code already using arrow functions, which would require transpiling for older browsers compatibility, you can add transpiling plugin to support ES2017.
In this case your code would be like:
function example() {
return Promise.all([
array.map(async (value) => {
try {
const response = await ajaxRequest(value.url);
return xmlParser(response);
} catch(err) {
console.error(err);
}
})
])
}
Above code will run all requests in parallel and return results when all requests finish. You may also want to fire and process requests one by one, this will also provide access to previous promise result if that was your question:
async function example(processResult) {
for(value of array) {
let result;
try {
// here result has value from previous parsed ajaxRequest.
const response = await ajaxRequest(value.url);
result = await xmlParser(response);
await processResult(result);
} catch(err) {
console.error(err);
}
}
}
Another solution is using Promise.all for doing this, i think is a better solution than looping arround the ajax requests.
const array = [{"id": 1, "url": "www.link1.com"}, {"id": 1, "url": "www.link2.com"}]
function example() {
return Promise.all(array.map(x => ajaxRequest(x.url)))
.then(results => {
return Promise.all(results.map(data => xmlParser(data)));
});
}
example().then(parsed => {
console.log(parsed); // will be an array of xmlParsed elements
});
Are there any better ways to pass previous promise results as
parameter in then() chain?
In fact, you can chain and resolve promises in any order and any place of code. One general rule - any chained promise with then or catch branch is just new promise, which should be chained later.
But there are no limitations. With using loops, most common solution is reduce left-side foldl, but you also can use simple let-variable with reassign with new promise.
For example, you can even design delayed promises chain:
function delayedChain() {
let resolver = null
let flow = new Promise(resolve => (resolver = resolve));
for(let i=0; i<100500; i++) {
flow = flow.then(() => {
// some loop action
})
}
return () => {
resolver();
return flow;
}
}
(delayedChain())().then((result) => {
console.log(result)
})