I'm trying to call an async utility function from NextJS API page, but gets undefined. The console prints the following when I go to localhost:300/api/hello
result = undefined
File 1: api/hello.js
export default function handler(req, res) {
getCountries().then(result=>{
console.log('result = ', result);
res.status(200).json({ status: 'ok', data: result.data });
}).catch(err=>{
console.log('error = ', err);
res.status(500).json({ status: 'error', error: err });
})
}
File 2: utils/getCountries.js
const getCountries = async () => {
var response = [];
var params = { action: "get_countries", ...authKey }
axios.get(APIFOOTBALL_URL, { params: params })
.then((result) => {
response = [...result.data];
return response;
}).catch((err) => {
throw err;
});
}
export default getCountries;
Changing the getCountries function to the following works, but I don't understand why. Isn't async returning a promise as well? If I have to write it in the async/await method, how should I go about?
File 2: utils/getCountries.js
const getCountries = () => {
return new Promise((resolve, reject) =>{
var response = [];
var params = { action: "get_countries", ...authKey }
axios.get(APIFOOTBALL_URL, { params: params })
.then((result) => {
response = [...result.data];
resolve(response);
}).catch((err) => {
reject;
});
})
}
CodePudding user response:
In an async
function, you must either await
your promise or you must return it. Without one of those, nothing actually waits for that promise or communicates back any result from it. Since the point of using async
is to be able to use await
, here's what that would look like:
const getCountries = async () => {
const params = { action: "get_countries", ...authKey }
const result = await axios.get(APIFOOTBALL_URL, { params: params });
return result.data;
}
Or alternately, you can just return the promise and don't need async
:
const getCountries = () => {
const params = { action: "get_countries", ...authKey }
return axios.get(APIFOOTBALL_URL, { params: params }).then(result =>{
return result.data;
});
}
A couple other notes about your code:
- There is no reason to use
var
any more. Uselet
orconst
instead in all modern Javascript. - There's no reason to copy the data with
response = [...result.data];
. You can just doreturn result.data
. - Your last code block is considered a promise anti-pattern. You never want to wrap an existing promise (the one return from the
axios.get()
call) with a manually created promise. It's just not necessary and often creates problems with error handling which indeed you have because you didn't callreject(err)
properly. - There's no reason for a
.catch()
that only doesthrow err
unless you also have some other code in that.catch()
(such as logging the error).
CodePudding user response:
In your first example of getCountries
, you are returning a promise of nothing (undefined), whereas in the second example, you are returning a promise of the response.
What happens in your first example is, axios.get
runs, but then nothing is really stopping the function from returning. The entire point of calling .then
on axios.get
is so that whatever is in the .then
callback gets executed after the promise is resolved. Otherwise you could have just written the code you're writing in .then
after the axios.get
call as usual, which you CAN do, if you use await. Using await
would stop the code execution until the promise is either resolved or rejected (which is why you should only use await
in a try-catch block.
If your first example is refactored as follows:
const getCountries = async () => {
const params = { action: "get_countries", ...authKey }
try {
const result = await axios.get(APIFOOTBALL_URL, { params: params });
const response = [...result.data];
return response;
} catch (err) {
console.error("An error occurred while trying to fetch countries: ", err);
throw err;
}
}
you end up blocking the execution of the code after the axios.get
call until that promise is resolved or rejected.
In short, the reason your first example doesn't work, is because your function returns undefined
(implicitly as there's no return statement in your first example) after the axios.get
call. Nothing stops it from returning, and so the promise your first example is returning is always resolved with undefined
value.
The reason your second example DOES work, is because when you're manually returning a promise, it is always pending until you manually resolve it with resolve(response);
or reject
.
If that doesn't help, think about it this way -> In the calling code, when you do getCountries.then
, The .then
part only executes when the promise is resolved. The the value it is resolved with is passed to the callback (which you use as result
). In your first example, getCountries
immediately resolves with a value of undefined
, so your callback gets the undefined
value, whereas in the second example, the promise that getCountries
returns is pending until resolve
is called in your code, which only happens after your API request promise resolves (since you're calling it in the .then
callback of axios.get
).
Also, in your first example, when you do return response;
in the .then
part of axios.get
, you are returning from the callback function of .then
. This does not return from the getCountries
function. Maybe that is causing you to think this should work, but doesn't.
Your first example can also be made to work if you do this:
const getCountries = async () => {
const params = { action: "get_countries", ...authKey }
return axios.get(APIFOOTBALL_URL, { params: params })
.then((result) => {
const response = [...result.data];
return response;
}).catch((err) => {
throw err;
});
}
because now, getCountries
is returning a promise of the response, instead of just returning a promise of undefined
.
Lastly, as @jfriend00 mentioned, there is no reason for a .catch()
that only throws the error again. The point of catch is to do something with the error (log it or handle it). The way you are using .catch()
right now will have the same behaviour as not using it at all.