Home > Software design >  difference using async vs return new Promise
difference using async vs return new Promise

Time:01-08

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:

  1. There is no reason to use var any more. Use let or const instead in all modern Javascript.
  2. There's no reason to copy the data with response = [...result.data];. You can just do return result.data.
  3. 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 call reject(err) properly.
  4. There's no reason for a .catch() that only does throw 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.

  • Related