Nested fetch() functions with conditional if statement in JavaScript - javascript

I have a function that runs a fetch() and at the very end of the fetch call it logs to the console.
I want to conditionally add a second fetch function before the end of the first. It also logs to the console.
I'm having trouble working out how to ensure the first log waits for the second fetch to complete and log its data before actually logging (whether or not it actually needs to run). I've been able to do this with several else statements, but I'd like to avoid the repetition.
Codepen # https://codepen.io/cfxd/pen/bGKmRZj
function alwaysRuns() {
fetch('https://api.spacexdata.com/v4/launches/latest')
.then(result => result.json())
.then(resultJson => {
const rocketId = resultJson.rocket;
const rocketName = resultJson.name;
if(rocketName.includes('Crew')) {
fetch(`https://api.spacexdata.com/v4/rockets/${rocketId}`)
.then(rocketResponse => rocketResponse.json())
.then(rocketJson => {
console.log('This fetch might not happen, depending on the conditional')
});
}
console.log('this should always log last whether the conditional causes the second fetch to run or not!');
});
}
alwaysRuns();

Use await
function alwaysRuns() {
fetch('https://api.spacexdata.com/v4/launches/latest')
.then(result => result.json())
.then(async resultJson => {
const rocketId = resultJson.rocket;
const rocketName = resultJson.name;
if(rocketName.includes('Crew')) {
await fetch(`https://api.spacexdata.com/v4/rockets/${rocketId}`)
.then(rocketResponse => rocketResponse.json())
.then(rocketJson => {
console.log('This fetch might not happen, depending on the conditional')
});
}
console.log('this should always log last whether the conditional causes the second fetch to run or not!');
});
}
alwaysRuns();
or even better
function alwaysRuns() {
fetch('https://api.spacexdata.com/v4/launches/latest')
.then(result => result.json())
.then(resultJson => {
const rocketId = resultJson.rocket;
const rocketName = resultJson.name;
if(rocketName.includes('Crew')) {
return fetch(`https://api.spacexdata.com/v4/rockets/${rocketId}`)
.then(rocketResponse => rocketResponse.json())
.then(rocketJson => {
console.log('This fetch might not happen, depending on the conditional')
});
}
}).then(() => {
console.log('this should always log last whether the conditional causes the second fetch to run or not!');
});
}
alwaysRuns();

Related

for loop with axios, how do I know if all data are loaded

this.allMyFacilities = response[1].data
for (let i = 0; i < this.allMyFacilities.length; i++) {
axios
.get(facilityUsed(this.allMyFacilities[i].id))
.then((response) => {
this.facilitiesOnSupplyChain[this.allMyFacilities[i].id] = response.data
})
.catch((err) => {
// An error will also be thrown if you use cancel.
console.error(err)
})
}
I have such a code. I don't know how many facilities do I have. that is I loop it, But each facility has its own Axios. I want to how do I know all data from the loop are completely loaded
You can use Promise.all for this:
this.allMyFacilities = response[1].data
const promises = this.allMyFacilities.map((myFacility) => {
return axios
.get(facilityUsed(myFacility.id))
.then((response) => {
this.facilitiesOnSupplyChain[myFacility.id] = response.data
return response.data;
}).catch((err) => {
// An error will also be thrown if you use cancel.
console.error(err)
});
});
Promise.all(promises).then((allResponsesArray) => /* do sth */)
Assuming that all the asynchronous calls can be made independently, you can leverage upon the Promise.all functionality to achieve this.
this.allMyFacilities = response[1].data
await Promise.all(this.allMyFacilities.map(myFacility) => {
return axios.get(facilityUsed(myFacility.id))
.then(data => this.facilitiesOnSupplyChain[myFacility.id] = data);
});
This will allow you to execute all the axios calls in parallel, and hence decrease the overall execution time of the whole process.
PS:
I have written an await statement hance assuming that you will be wrapping this functionality in an async function.

Why is my Firestore call not resolving before going further with the next .then()?

I thought I had a simple function:
database change trigger (.onUpdate)
find out which change is possibly important for a notification (prepareNotifications(change))
ask firebase if there are records that want a notification about that change (getDatabaseNotifications(changeWithNotification))
sent notifications (sentNotifications(changeWithNotification))
I'm stuck for a couple off days now on how to resolve the Firebase call before moving on.
tried to Promise.all([getDatabaseNotifications()])
tried to chain this function like this:
changes
then firebase call
then sent notifiactions
What is happening:
I get the changes,
The call to Firebase is done,
But before waiting for the result it moves on to sending notifications.
It finds no notifications in Firebase (but there are notifications!)
It's gathering the notifications (array [])
... here I push a test notification ...
It's sending 1 notification (the test notification)
Then it resolves the Firebase notifications (this should be resolved before).
Then the function stops without doing anything anymore.
This is how my function looks now. Can someone explain how I can wait on Firebase?
exports.showUpdate = functions.firestore
.document('shows/{showId}')
.onUpdate((change, context) => {
return prepareNotifications(change) // check if and which notifications to get out of the firebase database
.then(changes => {
console.log('changes');
console.log(changes);
if(changes) {
const gatherData = [];
changes.forEach(change => {
console.log('change');
console.log(change);
getDatabaseNotifications(change.showId, change.ring, change.ringNumber) // firebase call
.then(data => {
gatherData.push([...gatherData, ...data]);
console.log('gatherData');
console.log(gatherData);
})
.catch(err => {
console.log(err);
})
})
return gatherData;
}
return null;
})
.then(notifications => {
console.log('notifications');
console.log(notifications);
notifications.push(testData); // add some test notifications
if (notifications && notifications.length > 0) {
sentNotifications(notifications); // sent notifications
return 'sending notifications';
}
return 'no notifications to sent';
})
.catch(err => {
Sentry.captureException(new Error(`Showupdate sending notifications not ok. Error message: ${err.message}`));
})
});
Updated code which works! thanks to your examples.
exports.showUpdate = functions.firestore
.document('shows/{showId}')
.onUpdate((change, context) => {
return prepareNotifications(change) // check if and which notifications to get out of the firebase database
.then(changes => {
if(changes) {
return getDbRecords(changes);
}
})
.then(notifications => {
if (notifications && notifications.length > 0) {
sentNotifications(notifications); // sent notifications
return 'sending notifications';
}
return 'no notifications to sent';
})
.catch(err => {
Sentry.captureException(new Error(`Showupdate sending notifications not ok. Error message: ${err.message}`));
})
});
function getDbRecords(changes) {
const gatherData = [];
const gatherDataPromises = [];
changes.forEach(change => {
gatherDataPromises.push(
getDatabaseNotifications(change.showId, change.ring, change.ringNumber) // firebase call
.then(data => {
gatherData.push(...data);
})
);
});
return Promise.all(gatherDataPromises)
.then(() => { return gatherData }
);
}
This section of your code doesn't handle promises properly, it creates a bunch of work but then will return gatherData before any of it has happened, which is why you don't see any notifications:
if(changes) {
const gatherData = [];
changes.forEach(change => {
console.log('change');
console.log(change);
getDatabaseNotifications(change.showId, change.ring, change.ringNumber) // firebase call
.then(data => {
gatherData.push([...gatherData, ...data]);
console.log('gatherData');
console.log(gatherData);
})
.catch(err => {
console.log(err);
})
})
return gatherData;
}
Notably, you probably want that return gatherData to be chained off the set of promises that are generated by the entire set of calls to getDatabaseNotifications.
Something like:
if(changes) {
const gatherData = [];
const gatherDataPromises = [];
changes.forEach(change => {
console.log('change');
console.log(change);
gatherDataPromises.push(
getDatabaseNotifications(change.showId, change.ring, change.ringNumber) // firebase call
.then(data => {
gatherData.push([...gatherData, ...data]);
console.log('gatherData');
console.log(gatherData);
})
);
});
return Promise.all(gatherDataPromises)
.then(() => { return gatherData });
}
I removed the catch statement to allow the error to bubble up to the top level catch.
Caution: I have not tested this, as I don't have sample data or the code for getDatabaseNotifications, but the general approach should solve your current problem. Likewise, it allows all the calls to getDatabaseNotifications to run in parallel, which should be significantly faster than just awaiting on them in sequence.
That said, you do have other problems in this code -- for example, the return null just below the block I am discussing will likely lead you into trouble when you try to use notifications.push() in the following then() (but this also appears to be test code).
I think it's because of the async nature of the methods. So, instead of waiting "getDatabaseNotifications()" to finish it's job, it jumps into ".then(notifications =>{}" and in this case gatherData returns empty.
putting await before calling the method might work.
await getDatabaseNotifications(change.showId, change.ring, change.ringNumber)

Using Fetch in React to fetch data from server

I am trying to fetch data from the server(Node.js) with the following code:
componentDidMount = () => {
fetch('http://localhost:3000/numberofjobs')
.then(response => response.json())
.then(numberOfJobs => {
console.log(numberOfJobs)
})
}
That's my route in Node:
const handleNumberOfJobs = (req, res, Register) => {
Register.find({})
.then(users => {
const total = users.reduce((sum, { numberOfJobs }) => sum +
numberOfJobs, 0);
console.log(total);
})
.then(response => res.json(response))
}
One problem I'm having is the Front-end console.log is not showing up in the console, and I don't know why. In the server side, when the pages loads it does console.log the sum and everything, so it's working so I believe I am doing something wrong with React. I want to bring this info to my front-end so I could display it on the page.
Thanks a lot!!
TL;DR
There's a mistake in how you use arrow functions' implicit returns in your server side code.
The fix is to just add a return total; in the first .then(...) handler.
Details
First off, let's get it out: I agree with the comments on not neglecting error checks! (Be it fetch or anything else.)
Anyway: You use arrow functions in your .then(...) handlers. But that last statement in the first one is console.log(total). The return value of that call is undefined, which becomes the implicit return value of your arrow function. The promise then passes this on as the value of response in your second .then(...) handler. (You could verify that by adding console.log(response) in the second .then(...) handler.
The fix is to just add a return total; in the first .then(...) handler:
const handleNumberOfJobs = (req, res, Register) => {
Register
.find({})
.then(users => {
const total = users.reduce(
(sum, { numberOfJobs }) => sum +
numberOfJobs, 0
);
console.log(total); // <-- returns undefined
return total; // <-- pass on to next promise
})
.then(response => {
// console.log(response); // if you're curious
res.json(response)
})
}
}
Personal hint: Indent / lint your code for easier maintenance.

Asserting a callback was called with Jest (without React)?

I have a suite of tests but something is just not clicking regarding callback assertions. My feeling is that the done() parameter needs to be woven in, but I'm doing it wrong.
I basically have two function structures, where the callback is either nested inside of a single then statements, or a then inside of another then:
function foo(cb) {
return fetch('foo.com')
.then(
(res)=>{
res
.json()
.then(
(data) =>cb(data)
})
.catch(err => console.error(err))
}
and
function foo(cb) {
return fetch('foo.com')
.then(()=>cb())
.catch(err => console.error(err))
}
I'm looking to assert that the callback was called in both cases.
I have tried
describe('ugh why can't I figure this out'?, () => {
it('is confusing', (done) => {
const testCb = jest.fn()
foo(testCb);
expect(testCb).toHaveBeenCalled()
done();
//failed: expected testCb to be called, but it was not called
}
})
I'm not sure how to move forward- I'm not a fan of the spaghetti on the wall approach, so I'd rather understand how to implement jest for testing async code before I just start switching in different syntax. The above code seems like it should work because the callback is part of the function execution, so if the higher order function executes, the callback would necessarily be executed, and should have been "called". Obviously this reasoning is wrong since the test isn't passing, but I'm not quite seeing the way around this.
Thanks very much for any direction/insight :).
This jest example seems to match mine- what am I missing?
describe('drinkAll', () => {
test('drinks something lemon-flavored', () => {
let drink = jest.fn();
drinkAll(drink, 'lemon');
expect(drink).toHaveBeenCalled();
});
test('does not drink something octopus-flavored', () => {
let drink = jest.fn();
drinkAll(drink, 'octopus');
expect(drink).not.toHaveBeenCalled();
});
});
You're expect is being called before the fetch comes back. Do this:
foo(testCb)
.then(_ => {
expect(testCb).toHaveBeenCalled();
done();
})
.catch(err => {
done.fail(err);
});
By chaining on to the Promise returned by foo we ensure the fetch has come back. Once you go async you have to stay that way, you can't mix sync and async code like you did in your posted code:
const testCb = jest.fn()
foo(testCb); // this can take an arbitrary amt of time
expect(testCb).toHaveBeenCalled() // but this happens immediately
done();
FWIW you can also change this
return fetch('foo.com')
.then(
(res)=>{
res
.json()
.then(
(data) =>cb(data)
})
Into this:
return fetch('foo.com')
.then((res)=> res.json())
.then(cb)
.catch((err) => ...
The extra level of nesting promises is unnecessary and makes the code hard to read.

Dynamically update html content plain JS

So I have a script bound in my index.html, which literally has to fetch an array of jsons from an api, then dynamically update my page when get response.
But as a result I'm getting an as the only update of my page.
here is my script
fetch(url)
.then((resp) => resp.json())
.then((resp) => {
resp.results.forEach(item => {
fetch(item.url)
.then((response)=> response.json())
.then((response) => pokeArr.push(response))
.catch(console.log)
})
}).catch(console.log)
const filler = () => {
if(!pokeArr) return 0
pokeArr.map((i,j)=>{
return `<tr>${i.name}</tr>`
})
}
const pokeindex = () => {
document.getElementById('a').appendChild(document.createElement(filler()))
}
pokeindex()
When I'm consoling it, I can see in the console all the responses I get, so I'm at least doing the fetching part right.
There are a number of issues here:
You have parberakan in one place but pokeArr in another; in a comment you've suggested they're meant to be the same thing.
You don't declare parberakan/pokeArr anywhere in the quoted code.
Nothing in the quoted code is waiting for the various fetch calls to complete (not even the first, and esp. not the series of them in one of its then handlers).
The fetch calls aren't checking .ok; see the post on my anemic little blog.
document.createElement doesn't accept HTML strings. It accepts the tag name of the single element to create (e.g., document.createElement("div")).
filler doesn't return the result of map.
Passing console.log directly to catch isn't 100% reliable.
At a rough guess, I suspect you want to do something like this (see comments), but I do recommend stepping back from your current task and working through some basic tutorials:
// Create these functions first
const filler = (pokeArray) => { // Accept the array as a parameter
if(!pokeArray) return null; // Recommend null instead of 0 if the other return is an array
return pokeArray.map((i,j)=>{ // Return the result of `map`
return `<tr>${i.name}</tr>`;
});
};
const pokeindex = (pokeArray) => { // Accept the array as a parameter
// I'm assuming the element with id="a" is a `table` or `tbody`
document.getElementById('a').insertAdjacentHTML( // This accepts HTML
"beforeend",
fillter(pokeArray)
);
}
fetch(url)
.then((resp) => {
// Check .ok
if (!resp.ok) {
throw new Error("HTTP error " + resp.status);
}
return resp;
})
.then((resp) => resp.json())
.then((resp) => Promise.all(resp.results.map(item => { // Wait for all these to complete;
// result is an array of the
// resolution values
return fetch(item.url)
.then((response) => {
// Check .ok
if (!response.ok) {
throw new Error("HTTP error " + resp.status);
}
return response;
})
.then((response) => response.json());
})))
.then((pokeArray) => {
// Now we have the array and can do something
pokeindex(pokeArray);
})
.catch(error => { console.log(error); }); // Passing `console.log` directly isn't reliable
I may not have caught every last little thing, though. This is just to help get you pointed the right way.

Categories