I am trying to combine .fetch(), .then(). and .all(). But I can't get it right. My case looks like this:
fetch() a record from an API. (My Item)
then() Search in this record for more API links. (Here I find the links to the media that belong to the item. Each item can have any number of media).
all() Execute an API call for each of these links with .fetch()
then() if all previous promises are fulfilled, execute the render
function that depends on this data.
// Prepare Request Header
this.url_params = new URLSearchParams(window.location.search);
let httpHeader = {};
let request = new Request(api_itemURL + this.url_params.toString(), {
method: "GET",
headers: new Headers(httpHeader),
});
// The first fetch I have wrapped a seperate class. I still need it from other places.
omekaAPI
.getItemOnly(request)
.then(
function (result) {
this.itemObject = result;
let media_array = Array.from(result["o:media"], (x) => x["#id"]);
// Up to here it works wonderfully. I have list with all links. But after that it doesn't even jump into the all.
console.log(media_array);
return media_array;
}.bind(this)
)
.all(
media_array.map((media_array) =>
fetch(url).then(function () {
console.log("Its works!?!");
})
)
);
I don't know what I'm doing wrong and could use a push in the right direction.
Follow-Up Question
Today I have now tried to process the input. Unfortunately, I cheered too early. I only got back the underfilled Promise. With which my loop of course does not work. I then tried to append .then(). Both inside Promise.all() and after the .then() in which I assumed it. Unfortunately this has the same result. I thought then should go back a step and read about Promises in general. Is it possible that I have to do this with await/async? But then I have to write the complete function differently or? Also, my babel/uglify completely freaks out when I have one of these commands inside. It would be great if someone could give me a hint if I can get anywhere with my code here?
getItemWithAllMedia(request) {
return fetch(request)
.then((response) => {
return response.json();
})
.then((data) => {
console.log(data);
return data;
})
.then((result) => {
let media_links = Array.from(result["o:media"], (x) => x["#id"]);
return media_links;
})
.then((media_links) => {
return Promise.all(media_links.map((url) => fetch(url)));
})
.then((media_response) => {
/// debugging results from promise.all
console.log(media_response); // (2) [Response, Response]
console.log(media_reponse[0]); // ReferenceError: media_reponse is not defined
console.log(media_reponse[0].json()); // ReferenceError: media_reponse is not defined
/// desired function: all responses to json and push to an array
media_response.forEach((element) => {
media_data.push(element.json());
});
/// show created array for debugging
console.log(media_data);
return media_data;
})
Related
In my code below I get an empty array on my console.log(response) but the console.log(filterdIds) inside the getIds function is showing my desired data. I think my resolve is not right.
Note that I run do..while once for testing. The API is paged. If the records are from yesterday it will keep going, if not then the do..while is stopped.
Can somebody point me to the right direction?
const axios = require("axios");
function getToken() {
// Get the token
}
function getIds(jwt) {
return new Promise((resolve) => {
let pageNumber = 1;
const filterdIds = [];
const config = {
//Config stuff
};
do {
axios(config)
.then((response) => {
response.forEach(element => {
//Some logic, if true then:
filterdIds.push(element.id);
console.log(filterdIds);
});
})
.catch(error => {
console.log(error);
});
} while (pageNumber != 1)
resolve(filterdIds);
});
}
getToken()
.then(token => {
return token;
})
.then(jwt => {
return getIds(jwt);
})
.then(response => {
console.log(response);
})
.catch(error => {
console.log(error);
});
I'm also not sure where to put the reject inside the getIds function because of the do..while.
The fundamental problem is that resolve(filterdIds); runs synchronously before the requests fire, so it's guaranteed to be empty.
Promise.all or Promise.allSettled can help if you know how many pages you want up front (or if you're using a chunk size to make multiple requests--more on that later). These methods run in parallel. Here's a runnable proof-of-concept example:
const pages = 10; // some page value you're using to run your loop
axios
.get("https://httpbin.org") // some initial request like getToken
.then(response => // response has the token, ignored for simplicity
Promise.all(
Array(pages).fill().map((_, i) => // make an array of request promisess
axios.get(`https://jsonplaceholder.typicode.com/comments?postId=${i + 1}`)
)
)
)
.then(responses => {
// perform your filter/reduce on the response data
const results = responses.flatMap(response =>
response.data
.filter(e => e.id % 2 === 0) // some silly filter
.map(({id, name}) => ({id, name}))
);
// use the results
console.log(results);
})
.catch(err => console.error(err))
;
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
The network tab shows the requests happening in parallel:
If the number of pages is unknown and you intend to fire requests one at a time until your API informs you of the end of the pages, a sequential loop is slow but can be used. Async/await is cleaner for this strategy:
(async () => {
// like getToken; should handle err
const tokenStub = await axios.get("https://httpbin.org");
const results = [];
// page += 10 to make the snippet run faster; you'd probably use page++
for (let page = 1;; page += 10) {
try {
const url = `https://jsonplaceholder.typicode.com/comments?postId=${page}`;
const response = await axios.get(url);
// check whatever condition your API sends to tell you no more pages
if (response.data.length === 0) {
break;
}
for (const comment of response.data) {
if (comment.id % 2 === 0) { // some silly filter
const {name, id} = comment;
results.push({name, id});
}
}
}
catch (err) { // hit the end of the pages or some other error
break;
}
}
// use the results
console.log(results);
})();
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
Here's the sequential request waterfall:
A task queue or chunked loop can be used if you want to increase parallelization. A chunked loop would combine the two techniques to request n records at a time and check each result in the chunk for the termination condition. Here's a simple example that strips out the filtering operation, which is sort of incidental to the asynchronous request issue and can be done synchronously after the responses arrive:
(async () => {
const results = [];
const chunk = 5;
for (let page = 1;; page += chunk) {
try {
const responses = await Promise.all(
Array(chunk).fill().map((_, i) =>
axios.get(`https://jsonplaceholder.typicode.com/comments?postId=${page + i}`)
)
);
for (const response of responses) {
for (const comment of response.data) {
const {name, id} = comment;
results.push({name, id});
}
}
// check end condition
if (responses.some(e => e.data.length === 0)) {
break;
}
}
catch (err) {
break;
}
}
// use the results
console.log(results);
})();
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
(above image is an except of the 100 requests, but the chunk size of 5 at once is visible)
Note that these snippets are proofs-of-concept and could stand to be less indiscriminate with catching errors, ensure all throws are caught, etc. When breaking it into sub-functions, make sure to .then and await all promises in the caller--don't try to turn it into synchronous code.
See also
How do I return the response from an asynchronous call? and Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference which explain why the array is empty.
What is the explicit promise construction antipattern and how do I avoid it?, which warns against adding a new Promise to help resolve code that already returns promises.
To take a step back and think about why you ran into this issue, we have to think about how synchronous and asynchronous javascript code works together. Your
synchronous getIds function is going to run to completion, stepping through each line until it gets to the end.
The axios function invocation is returning a Promise, which is an object that represents some future fulfillment or rejection value. That Promise isn't going to resolve until the next cycle of the event loop (at the earliest), and your code is telling it to do some stuff when that pending value is returned (which is the callback in the .then() method).
But your main getIds function isn't going to wait around... it invokes the axios function, gives the Promise that is returned something to do in the future, and keeps going, moving past the do/while loop and onto the resolve method which returns a value from the Promise you created at the beginning of the function... but the axios Promise hasn't resolved by that point and therefore filterIds hasn't been populated.
When you moved the resolve method for the promise you're creating into the callback that the axios resolved Promise will invoke, it started working because now your Promise waits for axios to resolve before resolving itself.
Hopefully that sheds some light on what you can do to get your multi-page goal to work.
I couldn't help thinking there was a cleaner way to allow you to fetch multiple pages at once, and then recursively keep fetching if the last page indicated there were additional pages to fetch. You may still need to add some additional logic to filter out any pages that you batch fetch that don't meet whatever criteria you're looking for, but this should get you most of the way:
async function getIds(startingPage, pages) {
const pagePromises = Array(pages).fill(null).map((_, index) => {
const page = startingPage + index;
// set the page however you do it with axios query params
config.page = page;
return axios(config);
});
// get the last page you attempted, and if it doesn't meet whatever
// criteria you have to finish the query, submit another batch query
const lastPage = await pagePromises[pagePromises.length - 1];
// the result from getIds is an array of ids, so we recursively get the rest of the pages here
// and have a single level array of ids (or an empty array if there were no more pages to fetch)
const additionalIds = !lastPage.done ? [] : await getIds(startingPage + pages, pages);
// now we wait for all page queries to resolve and extract the ids
const resolvedPages = await Promise.all(pagePromises);
const resolvedIds = [].concat(...resolvedPages).map(elem => elem.id);
// and finally merge the ids fetched in this methods invocation, with any fetched recursively
return [...resolvedIds, ...additionalIds];
}
I would need to get the data returned by using the Neo4j driver for Node JS. My problem is, that i can print the value of 'online' out on console inside the .then call but i can't seem to get access to it outside of that part - I have tried returning record.get('onl'), assign it to a pre-defined variable outside the function but nothing works - all i get as result if i try to, for example, print the value of online out at the last line in this snippet, is Promise { <pending> }. I suppose I don't do the promise handling right, and I looked up lots of tutorials and examples, but I can't work it out. So: how could i assign the returned data (record.get('onl')) to var online and get actual result instead of the promise?
Thanks in advance :)
var online = session.run(cyp1, param).then(results => {
return results.records.map(record =>{
console.log(record.get('onl'))
return record.get('onl')
})
}).then(()=>{
session.close()
});
console.log(online)
Currently, you are assigning var online as a 'Promise Chain' & not a 'Resolved Promise'. You could use Async/Await this will allow you to write async code in a synchronous manner.
async function getRecords(){
const records = await session.run(cyp1, param);
return records.map(record => record.get('onl'))
}
const online = await getRecords();
Use try/catch/finally
try {
const online = await getRecords();
} catch (error) {
// do something
} finally {
await session.close()
}
If you wanted to continue using .then()
You need to use 'Promise Chaining' and pass the value 'Down the chain', this results in complex 'Callback'/'Promise Chain' hell.
session.run(cyp1, param).then(results => {
return results.records.map(record => record.get('onl'))
}).then((online)=> {
console.log(online)
}).catch(() => {
// do something
}).finally(() => {
session.close()
});
I have to code a tree component that displays multiple data, which worked fine with mocked data for me. The problem here is when I try to get data from servers, let me explain:
I have three main objects : Districts, buildings and doors. As you may guess, doors refers to buildingId and buildingID to districts. So to retrieve data and create my tree nodes, I have to do some http calls in forEach loops which is not asynchronous.
I won't share with you everything but just a minimized problem so I can get help easily:
This method retrieves a district array from the server and puts it in a local array:
async getDistricts(){
this.districtDataService.getDistrictData().toPromise().then(async districts => {
this.districts = await districts.results as District[];
});
}
On my ngOnInit :
ngOnInit() {
this.getDistricts().then(async () => {
console.log(this.districts);
for (const district of this.districts){
console.log(district);
}
})
The first console.log (in NgOnInit) returns an empty array, which is quite surprising because the first method puts the data in "this.districts". and logging data in the first method just after I put it in returns an array with my data. I guess it have something to do with the async/await I've used. Can anyone help?
EDIT 1: Tried to use this.getDistricts().finally() instead of this.getDistricts().then(), but didn't work.
EDIT 2: console.log in getDistrict get executed after the one before my loop. The expected behavior would be the opposite.
SOLVED: putting the for loop in a finally block after my HTTP call solves this. So as the answer says, I think I'm over engineering the async/await calls. I have to rethink my work based on this. Thank you everyone!
Well, you should return your Promise from getDistricts. Also you are very much over engineering and complicating the async/await concept. I understand you don't want to use Observables, but I would advise you to use them anyways.
With promises and async/await so you kinda see how to use them:
async getDistricts(): Promise<District[]> {
const { results } = await this.districtDataService.getDistrictData();
return results;
}
async ngOnInit(): Promise<void> {
this.districts = await this.getDistricts();
for (const district of this.districts){
console.log(district);
}
}
With Observable it would look like this:
getDistricts(): Observable<District[]> {
return this.districtDataService.getDistrictData().pipe(
map(({ results }) => results as District[])
);
}
ngOnInit(): void {
this.getDistricts().subscribe((districts) => {
this.districts = districts;
for (const district of this.districts){
console.log(district);
}
});
}
Just to provide whoever needs to make multiple http calls in a desired order.
as mentionned by others, i overcomplicated the concept of async await.
The trick was to use observables, convert them to Promises using .toPromise(), using .then() to get data into my variables, then making other async calls in finally block using .finally(async () => { ... }).
here's what my final code looks like :
async ngOnInit(): Promise<void> {
await this.districtDataService.getDistrictData().toPromise().then(response => {
this.districts = response.results as District[];
console.log(this.districts);
}).finally(async () => {
for (const district of this.districts){
await this.districtDataService.getBuildingsOfDistrict(district.id).toPromise().then(response => {
this.buildings = response.results as Building[];
console.log(this.buildings);
}).finally(async ()=> {
for(const building of this.buildings){
await this.districtDataService.getDoorsOfBuilding(building.id).toPromise().then(response => {
this.doors = response.results as Door[];
console.log(this.doors);
}).finally(async () => {
for(const door of this.doors){
await this.doorNodes.push(new districtNodeImpl(false,null,null,door,null));
}
})
await this.buildingNodes.push(new districtNodeImpl(false,null,building,null,this.doorNodes));
}
})
await this.dataSource.push(new districtNodeImpl(false,district,null,null,this.buildingNodes));
console.log(this.dataSource);
this.buildingNodes = new Array();
this.doorNodes = new Array();
}
})
Hope this will help ! have a nice day.
When calling a function that returns a promise, comes back as undefined unless async operators are removed, then returns ZoneAwarePromise, but contains no data.
I know the query returns data when the function executes, it however does not seem to pass that data to the actual return part of the function call.
I have looked at several Stack questions that have not answered this question including this question:
Async/Await with Request-Promise returns Undefined
This is using a REST endpoint to pull data, the console.logs do show the data is correct, however return comes back as undefined
this.allPeople.forEach(async person => {
const dodString = await this.getRelatedRecords(person); //undefined
}
This is the main function that returns a promise / data
async getRelatedRecords(person) {
// function truncated for clarity
// ...
//
console.warn('This async should fire first');
selPeopleTable.relationships.forEach(relationship => {
allRelationshipQueries.push(
arcgisService.getRelatedTableData(
selPeopleTable.url, [person[oidField.name]], relationship.id, relationship.name),
);
});
await Promise.all(allRelationshipQueries).then(allResults => {
console.log('Inside the Promise');
// The Specific node I am looking for
const data = allResults[1].results.relatedRecordGroups[0].relatedRecords[0].attributes.dod;
console.log(data); // Shows correctly as the data I am looking for
return data;
}).catch(function(data){
console.log('there might be data missing', data);
});
}
Removing the ASYNC operators cause the getRelatedRecords() to fire after the containing function and / or return a 'ZoneAwarePromise' which contains no data. I need getRelatedRecords() to fire first, then to run the rest of the code.
I can provide more snippets if need be.
Zone Aware Promise
When the Async operators are (I think) setup correctly
You need to return this as well:
await Promise.all(allRelationshipQueries).then(allResults => {
console.log('Inside the Promise');
// The Specific node I am looking for
const data = allResults[1].results.relatedRecordGroups[0].relatedRecords[0].attributes.dod;
console.log(data); // Shows correctly as the data I am looking for
return data;
})
return in the above block is returning but all of this is in the scope of the arrow function which is then(allResults => { so you also need to return this function like this:
return await Promise.all(allRelationshipQueries).then(allResults => {
Approach #2:
Second way would be to store that into variable like this:
let dataToReturn = await Promise.all(allRelationshipQueries).then(allResults => {
console.log('Inside the Promise');
// The Specific node I am looking for
const data = allResults[1].results.relatedRecordGroups[0].relatedRecords[0].attributes.dod;
console.log(data); // Shows correctly as the data I am looking for
return data;
}).catch(function(data){
console.log('there might be data missing', data);
});
return dataToReturn;
First of all, there are some issues with console.log in Google Chrome not functioning as expected. This is not the case as I am working in VSCode.
We begin with two async calls to the server.
promise_a = fetch(url)
promise_b = fetch(url)
Since fetch results are also promises, .json() will needed to be called on each item. The helper function process will be used, as suggested by a Stackoverflow user -- sorry lost the link.
let promiseResults = []
let process = prom => {
prom.then(data => {
promiseResults.push(data);
});
};
Promise.all is called. The resulting array is passed to .then where forEach calls process on item.json() each iteration and fulfilled promises are pushed to promiseResults.
Promise.all([promise_a, promise_b])
.then(responseArr => {
responseArr.forEach(item => {
process(item.json());
});
})
No argument is given to the final .then block because promiseResults are in the outer scope. console.log show confusing results.
.then(() => {
console.log(promiseResults); // correct results
console.log(promiseResults[0]); // undefined ?!?
})
Any help will be greatly appreciated.
If you are familiar with async/await syntax, I would suggest you not to use an external variable promiseResults, but return the results on the fly with this function:
async function getJsonResults(promisesArr) {
// Get fetch promises response
const results = await Promise.all(promisesArr);
// Get JSON from each response promise
const jsonResults = await Promise.all(results.map(r => r.json()));
return jsonResults
}
This is usage example:
promise_a = fetch(url1)
promise_b = fetch(url2)
getJsonResults([promise_a, promise_b])
.then(theResults => console.log('All results:', theResults))
Use theResults variable to extract necessary results.
You can try this, it looks the array loop is not going properly in the promise env.
Specifically: the promiseResults is filled after you are logging.
var resultAll = Promise.all([promise_a, promise_b])
.then(responseArr => {
return Promise.all(responseArr.map(item => return item.json()));
});
resultAll.then(promiseResults => {
console.log(promiseResults);
});