axios.post.then is called triggered after everything else - vue.js

I am trying to create a project where a person can create a question and an answer. I am using laravel and Vuex.
I would like to create a variable called Question_id with the response.data after I have called the axios.post to create the question. I would then like to call a function with this Question_id.
I am now noticing though that I cannot do this because when I try to set the question_id variable in .then portion of my axios.post, it happens after I call the other function. In other words, the .then portion happens after all my other code has ran.
qaForm(){
axios
.post("/api/question/create", this.questionForm)
.then(response => {
question = response.data;
question_id = question.id;
})
.catch(error => {
console.log(error.response);
});
addQuestion(question_id);
}
I can confirm this my consoling.out different steps. If I run this experiment:
qaForm(){
console.log("before axios post"); // runs 1st
axios
.post("/api/question/create", this.questionForm)
.then(response => {
console.log("inside axios.then"); // runs 3rd
question = response.data;
question_id = question.id;
})
.catch(error => {
console.log(error.response);
});
console.log("after axios post"); // runs 2nd
addQuestion(question_id);
}
I receive:
before axios post
after axios post
inside axios.then
Why is it this way? Am I making any mistakes? What are some possible workarounds?

Axios requests are asynchronous, and as such return a promise. That is why the code within then() is executed after the code that is below.
the simple fix is to move the code inside the response handler
qaForm(){
console.log("before axios post"); // runs 1st
axios
.post("/api/question/create", this.questionForm)
.then(response => {
console.log("inside axios.then"); // runs 2nd
question = response.data;
question_id = question.id;
console.log("after axios post"); // runs 3rd
addQuestion(question_id);
})
.catch(error => {
console.log(error.response);
});
}
This may seem a little strange when you first see it, it is my preferred way of dealing with asynchronous functions.
The JavaScript language however introduced async-await functionality that would allow you to rewrite the code in a way that you might find more intuitive though.
Note the use of async and await in the code.
async q0aForm() {
console.log("before axios post"); // 1st
let response = await axios.post("/api/question/create", this.questionForm)
console.log("no more axios.then"); // 2nd
question = response.data;
question_id = question.id;
console.log("after axios post"); // 3rd
addQuestion(question_id);
}

I saw your previous code, and everything is working right since is an axios petition. you could try for example in your store something like this.
async actionStore(){
const response = await axios.post('/api/question/create', this.questionForm;
if(isok){
//commit to store
return something;
} else return some error;
}
then in your method inside your component try this.
qaForm()
{
actionStore().then((response)=>{
if(response isok)
addQuestion(question_id);
});
}
It's just an example, cause I can't see all your code right now
remember that actions from store return promises.

If your using '.then' with POST, that my choise.
const blaBla = () => {
const POINT = '{YOUR POINT URL}'
const URL = '{YOUR POST VARIABLES}'
axios({
url: POINT,
data: `producturl=${URL}`,
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}).then((res) => console.log('res', res.data.content))
}
end worked
blaBla()

Related

Next.js 500 error and Axios error when calling API that uses environment variables

I'm building a wine pairing app in Next.js—where when a user clicks on a type of wine (i.e. chardonnay), it calls an API to return the suggested food pairings. This works with no issue when I use NEXT_PUBLIC for my environment variables, but I don't want to expose my private API key.
This is my first time using server-side environment variables with Next.js I understand that this needs to happen within the pages/api folder. API routing with Next.js is still something that I'm learning, so I've been following the docs, and I also found this tutorial which I followed that resulted in the 500 (Internal Server Error). I'm also getting an Axios error. This is a screenshot of both errors—please let me know if anything should be expanded, and I'll post another screenshot.
I also understand that I can use getStaticProps(), but this call is coming from a component rather than a page, and I understand from the docs that getStaticProps() must be called from a page.
This is what my .env.local looks like:
API_KEY=<my api key>
BASE_URL=https://api.spoonacular.com/
This is what my API call looks like (pages/api/wineWithFood.js)
import axios from 'axios';
export default async function wineWithFood(req, res) {
const {
query: { wine },
} = req;
const url = `${process.env.BASE_URL}food/wine/dishes?wine=${wine}&apiKey=${process.env.API_KEY}`;
const response = await axios.get(url);
res.status(200).json({
data: response.data,
});
}
This is the relevant code for what that call looks like in my component which is properly imported into the page where it belongs:
const getPairing = async () => {
axios.get(`/api/wineWithFood?wine=${wine}`, {
headers: {
Accept: 'application/json',
'Access-Control-Allow-Origin': '*',
},
})
.then((response) => response)
.then((response) => {
setData(response.data.pairings)
})
.catch((err) => console.log(err))
}
const handleChange = (e) => {
e.preventDefault();
setWine(e.target.value);
getPairing();
};
console.log(wine)
I see that the request isn't capturing the wine type, but when I console.log the wine, it's showing up in the browser console as expected. When I console.log the response.data from the API call, I get a status code of 400 with a message stating that the wine must not be empty.
Now, if I change my code to the following—I get the same errors and console.logs as I mentioned... but only on the first try! On the second try (clicking the same exact wine), the wine shows correctly in the browser console since I'm console.loging it, but I get that same 500 error in my console, however, now I can see all of the correct data in my terminal! That leads me to believe I'm doing something wrong on the frontend. Here's the tweaked code that results in this:
const getPairing = async (wine) => {
axios
.get(`/api/wineWithFood?wine=${wine}`, {
headers: {
Accept: 'application/json',
'Access-Control-Allow-Origin': '*',
},
})
.then((response) => response)
.then((response) => {
setData(response.data.pairings);
console.log(response.data.pairings);
})
.catch((err) => console.log(err));
};
const handleChange = (e) => {
e.preventDefault();
setWine(e.target.value);
getPairing(wine);
};
I'm happy to check out any other resources to help me out if that's a better answer to this question.
I finally figured this out and wanted to share the answer—which was staring me in the face.
I left my API call in pages/api/wineWithFood.js the same. I was right. The error was on the frontend. I got rid of the getPairing() function and put everything in the handleChange function. When I console logged my response on the frontend, I realized that the info I needed was res.data.data.pairings. I also changed the axios call by using e.target.value as the search query. I removed wine and setWine since it wasn't necessary. Here's the final code:
const handleChange = (e) => {
e.preventDefault();
axios
.get(`/api/wineWithFood?wine=${e.target.value}`, {
headers: {
Accept: 'application/json',
'Access-Control-Allow-Origin': '*',
},
})
.then((res) => {
setData(res.data.data.pairings);
});
};
I hope this can help someone out—also I'm open to feedback if there's a better way.

axios cancellation caught inside of then() instead of catch()

I making a multi-upload file form.
Upon user cancellation, once the corresponding axios call get cancelled using cancel(), I having a weird behaviour. My axios call get caught inside the then() whereas it should be caught inside of catch(). The response inside of then() returns undefined.
I am having a hard time figuring if I did something wrong on the front-end part, I think my call is may be missing some headers or maybe it's on the backend part ?
const payload = { file, objectId: articleId, contentType: 'article' };
const source = axios.CancelToken.source();
// callback to execute at progression
const onUploadProgress = (event) => {
const percentage = Math.round((100 * event.loaded) / event.total);
this.handleFileUploadProgression(file, {
percentage,
status: 'pending',
cancelSource: source,
});
};
attachmentService
.create(payload, { onUploadProgress, cancelToken: source.token })
.then((response) => {
// cancelation response ends up here with a `undefined` response content
})
.catch((error) => {
console.log(error);
// canceled request do not reads as errors down here
if (axios.isCancel(error)) {
console.log('axios request cancelled', error);
}
});
the service itself is defined below
export const attachmentService = {
create(payload, requestOptions) {
// FormData cannot be decamelized inside an interceptor so it's done before, here.
const formData = new FormData();
Object.entries(payload).forEach(([key, value]) =>
formData.append(decamelize(key), value),
);
return api
.post(resource, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
...requestOptions,
})
.then((response) => {
console.log(response, 'cancelled request answered here as `undefined`');
return response.data;
})
.catch((error) => {
// not caught here (earlier)
return error.data;
});
},
};
cancellation is called upon a file object doing
file.cancelSource.cancel('Request was cancelled by the user');
As suggested by #estus-flask in a comment, the issue is that I was catching the error inside of the service (too early). Thank you!
export const articleService = {
create(payload, requestOptions) {
// FormData cannot be decamelized inside an interceptor so it's done before, here.
const formData = new FormData();
Object.entries(payload).forEach(([key, value]) =>
formData.append(decamelize(key), value),
);
return api.post(resource, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
...requestOptions,
});
},
};

Proper setup of Vue.js methods to deal with Axios asynchronous request

I have Vue.js app that fetch some complex data from API with Axios and then visualize this data.
My code looks similar to this:
{
data: () => ({
data: null, // or Object, it doesn't matter now
loading: false,
errored: false,
loadingMessage: ''
}),
methods: {
loadData: function() {
this.loading = true;
this.loadingMessage = 'Fetching Data';
axios.get('api/url/').then((response) => {
this.data= response.data; // or this.$set(this, 'data', response.data), it doesn't matter now
this.loadingMessage = 'Process Data';
this.processData();
})
.catch(function () {
this.errored= true;
})
.then(function () {
this.loading = false;
})
},
processData: function() {
// process data
}
}
}
So then I click on the button in template, this button calls loadData() function.
It works fine, but fetching data takes some time and processing also takes some time and Vue change template and variables only when axios request is finished. So I see only Fetching Data message but not Process Data.
How can I show the user at what stage of processing the data now?
Maybe I should call the processData() function in watch methods, but that seems overkill to me.
Update
I ended up with setTimeout() wrap. See my answer below.
Vue has a function called nextTick, which is an asynchronous function that basically means "perform the following code after the next visual update". I usually use this method if I have visual updates like this to make.
I think the code in your example would look like this:
axios
.get('api/url/')
.then((response) => {
this.data= response.data;
this.loadingMessage = 'Process Data';
return this.$nextTick();
})
.then(() => {
this.processData();
})
.catch (etc ...
I am not completely sure. I usually work in webpack/babel-enabled environments, in which case I would just make the whole function async and write:
async function fn() {
const response = await axios.get('api/url/');
this.data = response.data;
this.loadingMessage = 'Process Data';
await this.$nextTick();
this.processData();
}
You can read about it here (https://v2.vuejs.org/v2/guide/reactivity.html#Async-Update-Queue)
Can you try changing your function as follows:
loadData: function() {
this.loading = true;
this.loadingMessage = 'Fetching Data';
axios.get('api/url/').then((response) => {
this.data= response.data; // or this.$set(this, 'data', response.data), it doesn't matter now
this.$nextTick(() => {
this.loadingMessage = 'Process Data';
this.processData();
})
})
.catch(function () {
this.errored= true;
})
.then(function () {
this.loading = false;
})
},
I tried to use $nextTick(), as #joachim-bøggild advised me. I even tried to use $forceUpdate() to archieve this goal. But for some reason that was not clear to me, the effect I needed was not observed. The console showed that the value has changed. But on the screen and in Vue Devtools old results were shown until the request was completed.
So I decided to supplement the question with an example and started to create demo on JsFiddle. To show the rendering delay that I need, I used setTimeout(() =>{ this.loading = false}, 1000) and there were no problems at all.
I ported this approach to my code and everything worked perfectly. Until I tried to remove this setTimeout(). The problem arose again.
Therefore, in the end, I ended up on this code design:
...
axios.get('api/url/').then((response) => {
this.data= response.data;
this.loadingMessage = 'Process Data';
setTimeout(() => {
this.processData();
this.loading = false;
}, 10); // if this value less than 10 error still occurs
})
.catch(function () {
this.errored= true;
})
...
If someone understands why this behavior is happening, please complete my answer.

Vue returning from a promise

I am trying to return some value from this dispatch
this.$store.dispatch('setValue', this.Value)
.then(response => {
console.log(response)
});
In my vuex action I have
.catch(error => {
if (error.response.status === 412) {
return "some message"
}
});
How can I pass the error back to the .vue file where the vuex dispatch is made?
I think the correct way of doing this is to have a status property in your store.
Your status object would consist out of error, success, loading.
So if your action throw exception you can handle it like this:
catch (error) {
commit("error", `Some Message`);
}
Your error mutation would look like this:
error(state, payload) {
state.status.success = false;
state.status.loading = false;
state.status.error = payload || false;
}
Your template would just listen on the store.state.status
<div v-if="store.state.status.error">{{store.state.status.error}}</div>
I might be wrong but in my personal opinion I feel it is wrong to use actions to return stuff. Your using the store so might as well leverage it best you can.
Other extra benefits is, you can indicate to your .vue file if api is loading or when something is successful.
What I ended up doing was pretty simple. I chained the catch to my dispatch:
this.$store.dispatch('setValue', this.Value)
.then(response => {
console.log(response)
})
.catch(error => {
if (error.response.status === 412) {
return "some message"
}
});
Then I returned the Axios call from the action:
return axios({
method: 'post',
url: `/mypath,
data: mydata,
json: true,
})
This means I could deal with the returned data/errors locally where I wanted to trigger an action.
Store:
.catch(error => {
if (error.response.status === 412) {
throw error
}
});
Vue element with async method:
try{
let response = await this.$store.dispatch('setValue', this.Value)
} catch(error) {
console.log(error)
});

Calling server.inject() POST request not calling handler in Hapi.js

I have a Jasmine test spec test_spec.js like this:
describe('my tests', () => {
it('POST should return 201 created', () => {
var req = {
method: 'POST',
url: '/api/v1.0/message',
payload: JSON.stringify({name: 'Ethan'})
};
server.inject(req, res => {
expect(res.statusCode).to.equal(201);
});
});
});
The route for the API call looks like this:
var routes = [{
path: '/api/v1.0/message',
method: 'POST',
handler: function(request, reply) {
reply('Success').created();
}
}];
exports.register = function(server, options, next) {
server.route(routes);
next();
}
When I run the tests, though, this particular test's expect() function doesn't get called because the server.inject() method doesn't call the response callback. In fact, not even the route handler method gets called (I checked with console.log statements). However, when I change the request method and the route from POST to GET, it works and the test calls the expect() method as expected. The test just doesn't work with POST requests. Am I doing it wrong?
Turns out that the problem was in the test call describe() snippet posted in my question. I neglected to call the done() function inside the server.inject() call. Once I added that, the POST test started getting called:
describe('my tests', () => {
it('POST should return 201 created', (done) => {
var req = {
method: 'POST',
url: '/api/v1.0/message',
payload: JSON.stringify({name: 'Ethan'})
};
server.inject(req, res => {
expect(res.statusCode).toEqual(201);
done();
});
});
});
The need to call the done() callback wasn't obvious to me from the Jasmine documentation. The call is necessary in order to postpone the spec completion until done() is called (meaning payload is posted).