I have the following API call that is supposed to return data for each of the IDs, however for v2, instead of returning information about each order, it only displays one of the IDs. The funny thing is all orders get displayed in the console log.
app.get("/all", function (req, res) {
api.get("/v3/orders/refunds")
.then((response) => {
// console.log(response.data[0].order_id)
// console.log(response)
for (var i = 0; i < response.data.length; i++) {
// console.log(response.data[i].order_id)
let ids = response.data[i].order_id;
// console.log(ids)
api.get(`/v2/orders/${ids}`)
.then((refundedOrders) => {
bothResponses = {
v3: response,
v2: refundedOrders
}
console.log(bothResponses)
res.status(200).json(bothResponses)
})
}
})
.catch((err) => {
console.log(err)
})
})
You have created a for loop to loop through the various orders, but inside that for loop, you call:
res.status(200).json(bothResponses)
You only get one response per request so when you call that on the first iteration of the for loop, no other responses will be sent for that request again. Subsequent calls to res.json() in that for loop will be ignored. In fact, they should have been outputting a warning to your console about "headers already sent" or something like that.
Instead, you need to accumulate the results for all the ids into an array and then send one response with all the data in it.
You could use Promise.all() to accumulate all the orders into an array and notify you when it's done like this:
app.get("/all", function(req, res) {
api.get("/v3/orders/refunds").then((response) => {
// console.log(response.data[0].order_id)
// console.log(response)
return Promise.all(response.data.map(item => {
return api.get(`/v2/orders/${item.order_id}`);
})).then(refundedOrders => {
let bothResponses = {
v3: response,
v2: refundedOrders
}
console.log(bothResponses);
res.json(bothResponses);
});
}).catch((err) => {
console.log(err);
res.sendStaus(500);
})
});
List of improvements:
Use .map() to iterate the array. Return a promise for each item in the array.
Use Promise.all() to monitor the array of promises and turn it into an array of ordered results.
Create one response to the http request and send that one response when all the data is available.
Send error status when there's an error in any of the api calls.
Remove .status(200) as that is already the default so it is unnecessary.
Add error handling for 2nd API call (by returning promise to the higher level so the .catch() will catch those 2nd API call errors too).
Related
i wrote an application in VueJS and i have to send first a get call to get the redirect url.
this.$axios.get('http://first-url.call').then(function(response) {
console.log(response.request.res.responseUrl)
let postUrl = response.request.res.responseUrl
}).catch(function(error){
console.log(error)
});
In my next call i want to use the "response.request.res.responseUrl" as post url
this.$axios.post(postUrl).then(function(response) {
console.log(response);
}).catch(function(error){
console.log(error)
});
Unfortunately, i cannot save the "response.request.res.responseUrl" response in a js variable. I'm not so familiar with async / await so maybe someone can help how i can store the first response into a value that i can use in my second call?
It's simpler to write it exclusively with async..await syntax:
try {
let response = await this.$axios.get('http://first-url.call')
let postUrl = response.request.res.responseUrl;
response = await this.$axios.post(postUrl)
...
} catch (err) {
...
}
It's beneficial to know the exact meaning of it. The key is correct promise chaining:
this.$axios.get('http://first-url.call')
.then((response) => {
let postUrl = response.request.res.responseUrl;
return this.$axios.post(postUrl)
})
.then((response) => {...})
.catch(...);
This way promises are executed in correct order and don't have unnecessary nested callbacks.
Be aware of incorrect use of function, it will lead to this problem.
Here is a solution without async/await: Make the second axios call within the first successful call. You need to use arrow functions for this so you keep the correct this on your Vue instance:
this.$axios.get('http://first-url.call').then((response) => {
console.log(response.request.res.responseUrl)
let postUrl = response.request.res.responseUrl
this.$axios.post(postUrl).then((response) => {
console.log(response);
}).catch(function(error){
console.log(error)
});
}).catch((error) =>{
console.log(error)
});
There are a number of ways to deal with this.
The simplest is to create a variable outside the callbacks. Though the following still relies on your first call completing before the second one.
let postUrl = null
this.$axios.get('http://first-url.call').then(function(response) {
postUrl = response.request.res.responseUrl
})
The next best (really the best) solution is to use async/await instead of .then() so you don't have callback scoping issues. This approach also guarantees you won't have a race condition because it will "await" for the first call to finish before making the second call.
The following would need to take place inside a function with async.
async function doWhatever() {
const response = await this.$axios.get('http://first-url.call')
const postResponse = await
this.$axios.post(response.request.res.responseUrl)
}
Finally, the last option is you nest your callbacks. This allows the second callback to have access to the scope of the first callback.
this.$axios.get('http://first-url.call').then(function(response) {
this.$axios.post(response.request.res.responseUrl).then(function(postResponse) {
// do something with the postResponse
})
})
Also, while the above code will work, it's usually better to chain promises.
this.$axios.get('...')
.then(function(response) {
// we return the promise so it can be handled in the next .then()
return this.$axios.post(response.request.res.responseUrl)
})
.then(function(postResponse) {
// do something with the postResponse
})
Notice how this last example starts to look a lot like async/await.
I have a few components that can be separate or on the same page. Each of these components uses the same Vuex state. Since they can each be used on other pages and still work, each of them dispatches a call to the same Vuex action which in turns calls a service that uses axios to get the JSON data.
All of this works great!
However, when I do have 2 (or more) of these components on a single page, that axios call gets called 1 time for each of the components. Initially, I went down the path of trying to see if data existed and get created a "last got data at" timestamp so I could just bypass the 2nd call. However, these are happening both on the components created event and are being essentially called at the same time.
So, enter debounce. Seems like the exact reason for this. However, when I implement it, it fails and is passing on to the next line of code and not awaiting. What am I doing wrong?
Agenda Component (one that uses the same state)
async created() {
await this.gatherCalendarData();
},
methods: {
async gatherCalendarData() {
await this.$store.dispatch('time/dateSelected', this.$store.state.time.selectedDate);
},
},
Month Component (another, notice they are the same)
async created() {
await this.gatherCalendarData();
},
methods: {
async gatherCalendarData() {
await this.$store.dispatch('time/dateSelected', this.$store.state.time.selectedDate);
},
},
The Action getting called
async dateSelected(context, data) {
let result = await getCalendarData(isBetween.date, context.rootState.userId);
await context.commit('SET_MONTHLY_DATA', { result: result.Result, basedOn: isBetween.date });
},
This getCalendarData method is in a service file I created to make api calls (below.)
This is the error that I receive (once for each component) that calls this action.
[Vue warn]: Error in created hook (Promise/async): "TypeError: Cannot read property 'Result' of undefined"
Which is referring to the 3rd line above: result: result.Result
API Service
const getCalendarData = debounce(async (givenDate, userId) => {
let response = await getCalendarDataDebounced(givenDate, userId);
return response;
}, 100);
const getCalendarDataDebounced = async (givenDate, userId) => {
let result = await axiosGet('/api/v2/ProjectTime/BuildAndFillCalendarSQL', {
givenDate: givenDate,
userID: userId,
});
return result;
};
Axios Wrapper
const axiosGet = async (fullUrl, params) => {
let result = null;
try {
let response = await axios.get(fullUrl, params ? { params: params } : null);
result = await response.data;
} catch(error) {
console.error('error:', error);
}
return result;
};
If I put console.log messages before, after and inside the getCalendarData call as well as in the getCaledarDataDebounced methods: (assuming just 2 components on the page) the 2 before logs show up and then the 2 after logs appear. Next the error mentioned above for each of the 2 components, then a single 'inside the getCalendarData' is logged and finally the log from within the debounced version where it actually gets the data.
So it seems like the debouncing is working in that it is only run a single time. But it appears that await call let result = await getCalendarData(isBetween.date, context.rootState.userId); is not truly Waiting.
Am I missing something here?
EDITS after Answer
Based on #JakeHamTexas' answer, my action of dateSelected is now (actual full code, nothing removed like above as to not confuse anything):
async dateSelected(context, data) {
console.log('dateSelected action');
let isBetween = isDateWithinCurrentMonth(data, context.state);
if (!isBetween.result) {
// The date selected is in a different month, so grab that months data
return new Promise(resolve => {
getCalendarData(isBetween.date, context.rootState.userId)
.then(result => {
console.log('inside promise');
context.commit('SET_MONTHLY_DATA', { result: result.Result, basedOn: isBetween.date });
context.commit('SET_SELECTED_DATE', isBetween.date);
context.commit('statistics/TIME_ENTRIES_ALTERED', true, { root: true });
resolve();
});
});
} else {
// The date selected is within the given month, so simply select it
context.commit('SET_SELECTED_DATE', data);
}
context.commit('CLEAR_SELECTED_TIME_ENTRY_ID');
},
And my API call of getCalendarData is now:
const getCalendarData = async (givenDate, userId) => {
console.log('getting calendar data');
let result = await axiosGet('/api/v2/ProjectTime/BuildAndFillCalendarSQL', {
givenDate: givenDate,
userID: userId,
});
return result;
};
The error is gone! However, it does not seem to be debouncing - meaning everything gets called 3 times. I would expect the dateSelected action to be called 3 times. But I would like to avoid the getting calendar data being called 3 times. If it helps, this is what the console looks like:
dateSelected action
getting calendar data
dateSelected action
getting calendar data
dateSelected action
getting calendar data
inside promise
inside promise
inside promise
You need to return a promise from your action. Returning a promise of undefined (which is what is currently happening) resolves immediately.
dateSelected(context, data) {
return new Promise(resolve => {
getCalendarData(isBetween.date, context.rootState.userId)
.then(result => {
context.commit('SET_MONTHLY_DATA', { result: result.Result, basedOn: isBetween.date });
resolve();
}
}
},
Additionally, a vuex commit does not return a promise, so it doesn't make sense to await it.
I'd like to intercept all api responses with the code != 200 in my main.js with the following code, dispatch an action and after that, show a toast showing the error message. I'm using vue-resource and my interceptor is the following:
Vue.http.interceptors.push(function(request, next) {
next(function(response) {
debugger;
if (response.status != 200) {
store.dispatch("errorAction", response);
}
});
});
But the code inside the callback is never reached...
And my api call is done this way. The java controller just throws an exception with the 500 error code.
Vue.http
.get(`http://${ADDRESS}/${store.state.module}/foo/exception`)
.then(() => {}, () => {});
I'm new with Promises and probably i'm messing things, but i don't want to be passing an error callback to every single promise. And what if my request is as follows:
export function getFoo(cb) {
Vue.http
.get(`http://${ADDRESS}/${store.state.module}/foo`)
.then(
response => {
return response.json();
},
() => {}
)
.then(foos => {
cb(foos);
});
}
I would like to get rid off () => {} and use the interceptor code to be run.
I think I'd had the same issue, so I tried to look at the documentation. Well, it is very simple, you just need to do something like this:
In main.js
Vue.http.interceptors.push(function(req) {
//Here you can add some headers, if needed
req.headers.set('awesomeHeader', 'owwnt')
return function(res) {
if( res.status == 200 || res.status == 201 || res.status == 202 ){ //Here you add the status codes that you'll work with, just like my example
//Sucess response
} else {
//Every time witch an request return a status differ form the list above, you can do whatever you want, for example you can redirect the page for a new one
window.location.href = `http://localhost:8080/#`
//If you want to display a notification, you need to import the component before, and then do something like it:
Notification.success({
title: 'error',
message: 'Unauthorized request!',
offset: 100
})
}
};
})
Does it answer you question? I hope it does.
If getting response like this-
data: Array(0)
length: 0
How to handling the error and how to show message the no matching data found in vuejs.
First of all, you should either wrap your axios request with try/catch if using async/await, or simply use then..catch methods.
Here's a simple example
axios.get('http://api.com')
.then((response) => {
if (response.data.length === 0) { // Lets check if response contains any items
// Do Your 'no items found' logic here
console.log('No items found')
return
} else { // We have items, handle them here!
// We have some items, lets log them
console.log(response.data)
}
})
.catch((error) => {
// Catch and handle any errors here
console.log(error)
})
I have my mongoose schema for a user's profile where they can add work experience (currently an array of objects in schema).
I have used the following code to find the user's profile and get the experience object as input then attach it to the array in schema and return the saved profile with experience:
Router.post('/experience',
Passport.authenticate('jwt', {session: false}), async (req, res) => {
try {
const myProfile = await Profile.findOne({user: req.user._id});
if (myProfile) {
const exp = {
title: req.body.title,
company: req.body.company,
location: req.body.location,
from: req.body.from,
to: req.body.to,
isCurrent: req.body.isCurrent,
description: req.body.description
};
// add to Profile experience array
Profile.experience.unshift(exp); // adds to beginning of array
const savedProfile = await Profile.save(); // have also tried myProfile.save() but that doesn't work too
if (savedProfile) {
console.log(savedProfile);
res.json({message: `Profile Updated Successfully`, details: savedProfile})
}
else { throw `Experience Details Not Saved`}
}
} catch (err) { res.json(err); }
});
The problem here is that the response is always an empty object and when I check my database, there is no experience saved. Is this code wrong? Same thing works with Promises but I want to try a new way of doing things.
The async-await pattern is another way to write Promise, the return value of the function is Promise.resolve(result) or Promise.reject(reason) of the whole async.
In the outer function, Router.post in this case, it has to use async-await, or then of Promise pattern, to deal with the returned Promise. Orelse, the async function would not have chance to run, as the returned Promise would be omitted.