I'm using an api that returns comment objects, the comments can have kids that are also comment objects.
I'm trying to retrieve the entire comment thread with recursion but its not working, as soon as a comment doesn't have a kid the function ends early and the result returned doesn't contain the entire comment thread.
How can I change the function so the resolve occurs after all comments have been checked to have no kids.
example of a comment object thats returned by the api
{
"id": 16020433,
"kids": [
16021684,
16021721,
16021659
],
"title": "Why isn't literally every train automated?",
"type": "story"
}
Here is my code:
function getCommentsWrapper(id) {
let comments = [];
return new Promise((resolve, reject) => {
function recurseComments(id) {
let url = `https://hacker-news.firebaseio.com/v0/item/${id}.json`;
let comment = {};
fetch(url)
.then(response => response.json())
.then(cmt => {
comment = cmt
comments.push(comment);
if (comment.kids) {
comment.kids.forEach(id => recurseComments(id));
} else {
resolve(comments)
}
})
}
recurseComments(id);
})
// call the function with
getCommentsWrapper("16033496")
.then(result => console.log(result));
You can use the call count technique, as in the code below. Or you can use a library such as Bluebrid. I think they have a lot of options.
I think also Promise.all can do the required, but in my past experience, adding new promises to Promise.all while the promise array is being handled was not reliable.
function getCommentsWrapper(id) {
var promises = [];
let count = 0;
let futureValue = new Promise((resolveAll, rejectAll)=>{
let comments = [];
let call = (id) => {
count++;
new Promise((resolve, reject) => {
let url = `https://hacker-news.firebaseio.com/v0/item/${id}.json`;
let comment = {};
fetch(url)
.then(response => response.json())
.then(cmt => {
comment = cmt
comments.push(comment);
if (comment.kids) {
comment.kids.forEach(id => call(id));
}
resolve();
count--;
if(count < 1){
resolveAll(comments);
}
})
});
};
call(id);
});
return futureValue;
}
// call the function with
getCommentsWrapper("16033496")
.then(result => console.log(result));
Related
I have the following structure of code:
var cdict = new Map();
fetch("randomurl")
.then(res => res.json())
.then(data => {
for (const obj of data.result) {
// insert stuff into Map cdict
}
counter(0).then(res => {
console.log(cdict);
})
// ^ here after calling of counter i need to do stuff
});
const cbegin = 0;
const ccount = 10;
function counter(cnt) {
if (cnt < ccount) {
setTimeout( () => {
cnt++;
fetch(someurl)
.then(res => res.json())
.then(data => {
for (const obj of data.result) {
// do stuff
}
})
.catch(err => {
console.log(err);
});
counter(cnt);
}, 1000);
}
}
Here after execution of counter(0) call and all its fetch requests, I wish to execute a line console.log(cdict);
How can this be achieved? And is this proper way to call fetch requests with delay of 1 second?
Don't mix setTimeout event queue callbacks with promise-based code -
const sleep = time =>
new Promise(resolve => setTimeout(resolve, time))
async function main()
{ console.log("please wait 5 seconds...")
await sleep(5000)
console.log("thanks for waiting")
return "done"
}
main().then(console.log, console.error)
Don't write .then(res => res.json()) every time you need some JSON. Write it once and reuse it -
const fetchJSON(url, options = {}) =>
fetch(url, options).then(res => res.json())
async function main()
{ const data = await fetchJSON("path/to/data.json")
console.log("data received", data)
return ...
}
main().then(console.log, console.error)
Don't attempt to declare variables outside of Promises and modify them later. You cannot return the result of an asynchronous call. Asynchronous data needs to stay contained within the promise or you will be chasing hard-to-find bugs in your code -
async function main(urls)
{ const result = []
for (const u of urls) // for each url,
{ result.push(await getJSON(u)) // await fetch and append to result
sleep(1000) // wait 1 second
}
return result
}
const myUrls =
[ "foo/path/data.json"
, "another/something.json"
, "and/more/here.json"
]
main(urls)
.then(result => /* counter logic */)
.then(console.log, console.error)
Continue abstracting as you see fit -
// give reusable functions a name; use parameters for configurable behaviour
async function fetchAll(urls, delay = 100)
{ const result = []
for (const u of urls)
{ result.push(await getJSON(u))
sleep(delay)
}
return result
}
async function counter(urls)
{ const results = await fetchAll(urls) // use our generic fetchAll
const cdict = /* counter logic... */
return cdict
}
const myUrls =
[ "foo/path/data.json"
, "another/something.json"
, "and/more/here.json"
]
counter(urls).then(console.log, console.error)
As you can see async and await prevent nesting that occurs with the use of setTimeout or .then callbacks. If you use them correctly, your code remains flat and you can think about your code in a synchronous way.
I've got the following code that retrieves data asynchronously and caches it for performance optimization purposes:
let cache = {}
const optimizedFetch = async (url) => {
if (url in cache) {
// return cached result if available
console.log("cache hit")
return cache[url]
}
try {
const response = await fetch (url)
const json = response.json();
// cache response keyed to url
cache[url] = json
return json
}
catch (error) {
console.log(error)
}
}
optimizedFetch("https://jsonplaceholder.typicode.com/todos").then(console.log)
The approach above works fine but if a second request to the same url comes while the first one is still awaited then a second fetch will be fired.
Could you please advice me on the ways to improve that scenario?
Thanks in advance.
let cache = {};
let awaits = {};
const optimizedFetch = (url) => {
return new Promise((resolve, reject) => {
if (url in cache) {
// return cached result if available
console.log("cache hit");
return resolve(cache[url]);
}
if (url in awaits) {
console.log("awaiting");
awaits[url].push({
resolve,
reject
});
return;
}
console.log("first fetch");
awaits[url] = [{
resolve,
reject
}];
fetch(url)
.then(response => response.json())
.then(result => awaits[url].forEach(({
resolve
}) => resolve(result)))
.catch(error => awaits[url].forEach(({
reject
}) => reject(error)))
.finally(() => {
delete awaits[url];
});
});
};
optimizedFetch("https://jsonplaceholder.typicode.com/todos")
.then(({
length
}) => console.log(length));
optimizedFetch("https://jsonplaceholder.typicode.com/todos")
.then(({
length
}) => console.log(length));
Just save Promise as a value.
Working example:
let cache = {}
let totalRequests = 0;
const optimizedFetch = async(url) => {
if (cache[url]) return cache[url]; // return what we have
totalRequests++;
const fetchedRequest = fetch(url)
.then(response => response.json())
.then(json => {
cache[url] = json; // <-- we replace "Promise" result with json result
return json;
});
cache[url] = fetchedRequest; // <-- we keep Promise as a result
return fetchedRequest;
}
for (let i = 0; i < 5; i++) {
console.log('Setting request #', i);
optimizedFetch("https://jsonplaceholder.typicode.com/todos").then(result => console.log('Requests:', totalRequests, 'Result:', result.length));
}
Explanation:
totalRequests is just for testing purposes - it shows us how many "real" fetches we made
if we have an answer in cache - we return that answer
if we do not have answer - we create new fetch and put it as answer
P.S. My answer is getting downvotes, but I do not understand why... It is working cache example. Please, people, if you downvote, comment above, so I will understand my mistake if any. Thank you
I am trying to replace the loop that send out http requests using Axios. http is an Axios object and returns a promise. I want to change the code so that I use Promises.all() instead of a loop. I am trying to create a Promise, push into an array and then pass on to Promises.all. I only get empty arrays in my promises array.
I would appreciate any pointers on what I am doing wrong.
// Converting this
responseData = [];
for (const record of response.records) {
let response = await http.get('/records/' + record.id);
responseData.push(response.data.data);
}
// I am trying to convert to this ..
let promises = [];
for (const record of response.data.data) {
let promise = new Promise((resolve, reject) => {
let response = http.get('/records/' + record.id)
.then(response => {
return response.json();
})
.then(resp => {
// console.log(resp.data.data);
//return resp.data.data
resolve(resp.data.data);
});
return response;
});
promises.push(promise);
}
Promise.all(promises).then(records);
Not entirely sure of whether response.json() is required, and where the .records and .data.data should go ... but this might work:
const promises = [];
for (const record of response.records) {
promises.push(
http.get('/records/' + record.id)
.then(response => response.json()) //maybe?
.then(response => response.data.data)
)
}
Promise.all(promises).then(records => { /* do something */ } );
or use map:
const promises = response.records.map(record =>
http.get('/records/' + record.id)
.then(response => response.json()) //maybe?
.then(response => response.data.data)
)
Promise.all(promises).then(responseData => {/* do something */} );
I have the following code that is used to get JSON data from an Amazon Web Server API.
var json1 = new Promise((resolve, reject) => {
fetch(url[0])
.then(r => {
resolve(r.json())
})
.catch(err => {
reject(err)
})
})
I have this repeating 14 times using different urls and json vars and have it return the promises at the end using.
return Promise.all([json1,json2,json3,json4,json5,json6,json7,json8,json9,json10,json11,json12,json13,json14]).then(function(values) {
return values;
});
This works, but it takes up 150+ lines. I want to make a for loop that runs through the same code using a for loop. I created this...
for(var jsonCount = 0;jsonCount<url.length-1;jsonCount++){
jsonArr[jsonCount] = new Promise((resolve, reject) => {
fetch(url[jsonCount])
.then(r => {
resolve(r.json())
})
.catch(err => {
reject(err)
})
})
}
This doesn't work because the promise functions come back as undefined even though it is called by an await function.
const data = await fetchURL(urlToQuery())
Does anyone have suggestions to make this work? There is JSON being returned.
Thanks for your help.
Edit: Here is a larger chunk of the code.
function fetchURL(urls) {
let fetchJson = url => fetch(url).then(response => response.json());
Promise.all(urls.map(fetchJson)).then(arr => {
return arr;
});
(async function() {
const data = await fetchURL(urlToQuery())
console.log(data);
for(var r=0;r<numStations;r++){
if (data[r] == ""){
onlineArr[r] = false;
wdDataArr[r].push(cardinalToDeg(stationHistAvgArr[r]));
wsDataArr[r].push(0);
You can use .map for the loop. But don't use new Promise. You don't need a new promise when fetch already provides you with one.
Also, call your array urls instead of url. A plural will be a good indication for the reader of your code that indeed it is a collection of URLs.
Here is how it could look:
let fetchJson = url => fetch(url).then(response => response.json());
Promise.all(urls.map(fetchJson)).then(arr => {
// process your data
for (let obj of arr) {
console.log(obj);
}
});
I think this example can helps you:
// Mock async function
const getDataAsync = callback => {
setTimeout(
() => callback(Math.ceil(Math.random() * 100)),
Math.random() * 1000 + 2000
)
}
// Create the promise
const getDataWithPromise = () => {
return new Promise((resolve, reject) => {
try {
getDataAsync(resolve);
} catch(e) {
reject(e);
}
});
}
// Using the promise one time
getDataWithPromise()
.then(data => console.log("Simple promise:",data))
.catch(error => console.error(`Error catched ${error}`));
// Promises compound: Promise.all
const promise1 = getDataWithPromise();
promise1.then(data => console.log("promise1 ends:",data));
const promise2 = getDataWithPromise();
promise2.then(data => console.log("promise2 ends:",data));
const promise3 = getDataWithPromise();
promise3.then(data => console.log("promise3 ends:",data));
const promise4 = getDataWithPromise();
promise4.then(data => console.log("promise4 ends:",data));
const promise5 = getDataWithPromise();
promise5.then(data => console.log("promise5 ends:",data));
Promise.all([promise1,promise2,promise3,promise4,promise5])
.then(data => console.log("Promise all ends !!",data));
Hope this helps
you will have issues with closure and var variable capture.
You may want to change var to let to capture the right value in the closure so that url[jsonCount] is actually what you want.
also I think it would be much easier to do something like that in one line :)
let results = [];
for(let i = 0; i < urls.length; ++i) results.push(await (await fetch[urls[i]]).json());
This is a good use for map, mapping urls to promises...
function fetchUrls(urls) {
let promises = urls.map(url => fetch(url))
return Promise.all(promises).then(results => {
return results.map(result => result.json())
})
}}
// url is your array of urls (which would be better named as a plural)
fetchUrls(url).then(results => {
// results will be the fetched json
})
Using the async/await syntax (equivalent meaning)
// this can be called with await from within another async function
async function fetchUrls(urls) {
let promises = urls.map(url => fetch(url))
let results = await Promise.all(promises)
return results.map(result => result.json())
}
The requirement is finishing the current function before moving to the next call:
var current_data = something;
Run(current_data).then((data1) => {
Run(data1).then(data2 => {
Run(data2).then(data3 => {
// and so on
})
})
});
The example above is only possible if I know exactly how much data I want to get.
In order to make the nested promises part of promise chain, you need to return the nested promises.
Run(current_data).then((data1) => {
return Run(data1).then(data2 => {
return Run(data2).then .....
});
});
I'm gonna assume your data is paginated and you don't know how many pages there are, therefore you can use a while loop with await inside of an async function like so:
(async function() {
var currentData = someInitialData;
// loop will break after you've processed all the data
while (currentData.hasMoreData()) {
// get next bunch of data & set it as current
currentData = await Run(currentData);
// do some processing here or whatever
}
})();
You can use the async-await to make code more readable.
async function getData(current_data){
let data1 = await Run(current_data)
let data2 = await Run(data1);
let result = await Run(data2);
return result;
}
Calling the getData function
getData(data)
.then(response => console.log(response))
.catch(error => console.log(error));
Try to avoid nested promises. If you need to call a series of promises, which depend on the previous call's response, then you should instead chain then like the following following -
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('foo');
}, 1000);
});
promise1.then((response) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(response + ' b');
}, 1000);
});
}).then((responseB) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(responseB + ' c');
}, 1000);
});
}).then((responseC) => {
console.log(responseC); // 'foo b c'
})
if your code can support async-await then what Mohammed Ashfaq suggested is an alternative.
If you are executing the same function over and over again but on different data, I would make a recursive function that returns return a Promise.
I just look at my example below using an an array of numbers, you can edit it to your current case.
var current_data = [1,2,4,5,6]
function Run(data){
if(data.length === 0)
return Promise.resolve(data);
return new Promise((resolve, reject)=>{
//your async operation
//wait one second before resolving
setTimeout(()=>{
data.pop()
console.log(data)
resolve(data)
},1000)
})
.then((results)=>{
return Run(results)
})
}
Run(current_data)