[Vuejs]-Does the result of an api call in JS have to be awaited?

0👍

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
}

0👍

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

0👍

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.


Displaying the data

As posted setReportData does not notify its caller of when report data is available, either by means of a callback or by returning a promise. All it does is save the result in this.reportDatatableEmployeeInfo and dies.

Without using callbacks, setReportData is left with two choices

  1. Return a promise that is fulfilled with, say,resultsEmployeeInfo or undefined if an error occurs:

     setReportData( employeeId) {
       this.isBusy = true;
    
       // return the final promise:
    
       return getEmployeeInfo( 
           employeeId
         )
        .then( (resultsEmployeeInfo) =>
           (this.reportDatatableEmployeeInfo = resultsEmployeeInfo) 
         )
        .catch(() => {
           this.alerts.error('An error has occurred while fetching the data');
           // return undefined 
         })
         .finally(() => {
           this.isBusy = false;
         });
     },
    

    which could be used in a calling sequence using promises similar to

     if(!this.busy) {
       this.setReportData(someId).then( data =>  {
         if( data) {
            // update page
         }
     }
    

    If you wanted to make the call in an async context you could use await:

     if(!this.busy) {
       const data = await this.setReportData(someId);
       if( data) {
            // update page
       }
     }
    
  2. Update the page from within setReportData after the data becomes available ( as shown as a comment in the first part of this answer). The method should probably be renamed from setReportData to getReportData or similar to reflect its purpose.

Leave a comment