Home > Net >  Does the result of an api call in JS have to be awaited?
Does the result of an api call in JS have to be awaited?

Time:04-09

I have the following endpoint in a class called UserApi.js:

const controller = 'User';
...
export async function getEmployeeInfo(employeeId) 
{
  const query = createQueryFromObject({employeId});
  const response = await get(`/${controller}/EmployeeInfo?${query}`);
  return retrieveResponseData(response, []);
}

This is going to get the required information from an action method in the backend of UserController.cs.

Now, say that I want to display this information in EmployeeView.vue class, do I have to await it again? Why or why not? Initially, I would say no, you don't, as you already dealt with the await/async in the UserApi.js class, but what about the Promise.resolve? Please explain.

  methods: {
    async setReportData(
     employeeId
    ) {
      this.isBusy = true;
      Promise.resolve(getEmployeeInfo(
        employeeId
      )).then((resultsEmployeeInfo) => {
        this.reportDatatableEmployeeInfo = resultsEmployeeInfo;
      })
        .catch(() => {
          this.alerts.error('An error has occurred while fetching the data');
        })
        .finally(() => {
          this.isBusy = false;
        });
    },

Update: ....

 * @param {Object} response
 * @param {any} defaultData
 * @param {Function} predicate
 * @returns {Promise}
 */
export function retrieveResponseData(response, defaultData = null, predicate = (predicateResponse) => predicateResponse) {
  const data = predicate(response) ? response.data : null;

  return data || defaultData;
}

CodePudding user response:

You need to await it since a function declared with async keyword ALWAYS returns a Promise, even if you do only synchronous stuff inside of it, hence you need to await or "thenize" it to access the value it resolved to. That depends from the implementation details of async functions which are just generators that yield promises.

If this concerns you because you work inside sync modules and don't like to use async functions just to execute more async functions, there's a good news, TOP-LEVEL await MODULES proposal is at stage 4, so it'll very soon be shipped with the next ECMA version. This way you will be able to await inside modules as if they were wrapped by async functions ! https://github.com/tc39/proposal-top-level-await

CodePudding user response:

I can't tell if you need to await it again, because I can't tell what retrieveResponseData does. It might take the resolved value and wrap it in a fresh promise, which would then require callers of getEmployeeInfo to await the result.

Here's the why:

A Promise is a wrapper around a value

await unwraps a Promise. So does the .then() handler you can register with a Promise (but the value is only unwrapped within the function you provide to .then()).

Just like a gift in the real world, once something has been unwrapped, you don't need to unwrap it again. However, very conveniently for us, you can still use await on a value that is not wrapped in a Promise, and it will just give you the value.

You can wrap any value in a Promise, like so:

let wrappedFive = Promise.resolve(5)
//> wrappedFive is a Promise that must be unwrapped to access the 5 inside it

// this does _exactly_ the same thing as the above
let wrappedFive = new Promise(resolve => {
    resolve(5)
})

Sometimes you end up in a situation where you can't use await, because you're in a function that cannot be marked async. The lifecycle methods of front-end frameworks like React (and possibly Vue) are like that: the framework needs each lifecycle method to do its job and be done immediately. If you mark the lifecycle method as async, you can often prevent it from having the intended effect.

In cases like that, you often need to use chained .then() handlers, which is a little uglier, but it works:

componentDidMount() {
    // this API call is triggered immediately by lifecycle,
    // but it basically starts a separate thread -- the rest
    // of this function does not wait for the call to finish
    API.getUserInfo()
    .then(userInfo => {
        // this happens after the API call finishes, but
        // componentDidMount has already finished, so what happens
        // in here cannot affect that function
        this.setState({ username: userInfo.username })
    })

    // this happens immediately after the API call is triggered,
    // even if the call takes 30 seconds
    return 5
}

Note that a Promise does not actually start a separate thread -- these all happen in the same thread that executes the lifecycle method, i.e. the browser's renderer thread. But if you think of the codepath that executes, a Promise that you don't wait for basically introduces a fork into that codepath: one path is followed immediately, and the other path is followed whenever the Promise resolves. Since browserland is pretty much a single-threaded context, it doesn't really hurt you to think of creating a Promise as spawning a separate thread. This is a nuance you can ignore until you are comfortable with asychronous patterns in JS.


Update: retrieveResponseData does not appear to return a Promise. I could be wrong, if predict returns a Promise, but if that were true, then the ternary would always return response.data because unwrapped Promises are truthy values (even Promise.resolve(false) is truthy).

However, anyone who calls getEmployeeInfo will have to wait it, because that function is marked async, and that means it returns a Promise even if nothing inside it is is asynchronous. Consider this extreme example:

// this function returns a number
function gimmeFive() {
    return 5
}

// this function returns a Promise wrapped around a number
async function gimmeFive() {
    return 5
}

CodePudding user response:

Async function getEmployeeInfo awaits the result of the get call in order to return the value returned by a call to retrieveResponeData.

Assuming neither get nor retrieveResponeData errors, the value syntactically returned in the body of getEmployeeInfo is used to resolve the promise object actually returned by calling getEmployeeInfo.

Promise.resolve is not needed to convert the result of calling getEmployeeInfo into a promise because, given async functions return promises, it already is.

It doesn't matter if retrieveResponseData returns a promise or not: standard async function processing waits for a returned promise value to be settled before resolving the promise returned when calling the async function.

Async function setRreportData is declared as async but written using chained promise handler methods to process data and error conditions in order - the async declaration could be omitted, but may be useful if modifications are made.

The results can only be used to update the page at a time when the data has finished being obtained and extracted, shown as a comment in the following code:

setReportData( employeeId) {
  this.isBusy = true;
  getEmployeeInfo(
    employeeId
  ).then((resultsEmployeeInfo) => {
    this.reportDatatableEmployeeInfo = resultsEmployeeInfo;

    //  At this point the result in  this.reportDatatableEmployeeInfo can be used to update the page

  })
    .catch(() => {
      this.alerts.error('An error has occurred while fetching the data');
    })
    .finally(() => {
      this.isBusy = false;
    });
},

Displaying the data using EmployeeView.vue class must wait until the data is available. The simplest place to insert updating the page (in the posted code) is within the then handler function inside setReportData.

  • Related