Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Faking Ajax calls: Reading Synchronously

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

The categories data is now available as a global variable: window.categories.

In sidebar.vue, we're calling fetchCategories(), which lives in our fancy new categories-service module. You can find this at assets/js/services/categories-service.js.

To switch this from an Ajax call to the global variable, just return window.categories. Celebrate by removing the axios import on top.

/**
* @returns Array
*/
export function fetchCategories() {
return window.categories;
}

This is what I like about centralizing the fetchCategories() method: we don't need to run around and change our code in a bunch of places: just here. Well... that's not quite true yet. One problem is that our function does not return a Promise anymore! It now returns an array. That could affect code that uses this.

Whoops! We're Returning Different Data

But... let's ignore that for a moment! Move over and refresh. Bah! It's broken:

Cannot read property hydra:member of undefined

We did change the function from returning a Promise to returning an array. But that's actually not what broke our code! The real problem is that, down in created(), fetchCategories() no longer gives us a response object with a data key!

Let's back up: it's actually ok to use await on a non-async function: JavaScript just grabs the return value from the function and gives it to us: no error. The real problem is that the value that the previous code gave us was a response object... but now we're returning an array of categories. This means that response.data is undefined!

The simplest way to fix this is right here: comment out the old this.categories line and directly say this.categories = fetchCategories(). We don't need the await anymore, but it doesn't hurt anything.

... lines 1 - 50
<script>
... lines 52 - 54
export default {
... lines 56 - 79
async created() {
this.categories = await fetchCategories();
//this.categories = response.data['hydra:member'];
},
};
</script>
... lines 87 - 105

Simple enough! Back on the browser, it's already showing the categories on the left. And if we reload... yes! The categories are instantly there! Woo! There is still a slight delay before the styles load, but that will only happen in dev mode: we did that to allow hot module replacement. So, mission accomplished!

Returning a Promise from the Sync Method

One of the cool things about isolating our Ajax calls into functions like fetchCategories() is that, when we call that function, we don't need to know or care if it's talking to an API, or which API, or if we're just loading the data locally from local storage or a variable.

But... that's not really true right now. Because when we changed this from using the API to the global variable, we changed what this function returned! We changed it from returning a Promise to an array and we also changed the actual data that's returned from a response object with data and hydra:member properties to an array. This meant that, once this function was synchronous, we needed to update any code that called this.

And... that's fine. If you know that you're going to load categories synchronously via window.categories, then you can just update your code to reflect that. It's not a huge problem.

But when we changed to the global variable, we could have also written our function in a way that did not break any code that used it: we could have returned a Promise.

Let's try that: return new Promise(). This needs one argument: a callback with resolve and reject. If you've never seen a Promise like this, check out our ES6 tutorial all about them. They're fascinating.

... lines 1 - 3
export function fetchCategories() {
return new Promise((resolve, reject) => {
... lines 6 - 10
});
}

Anyways, once our work is done - which will be instantly since we don't need to make any Ajax calls, call resolve() and pass it the data that we want the promise to return. Now, we're not really making an Ajax request... so we can't exactly return the same data as before because... we don't have a response. But we know that what we really care about is that this Promise returns a object with a data key and a hydra:member key below it. Let's at least fake that here: add data set to an object and hydra:member set to window.categories. I'll remove the extra return at the bottom and, above the function, once again, advertise that we return a Promise!

... lines 1 - 3
export function fetchCategories() {
return new Promise((resolve, reject) => {
resolve({
data: {
'hydra:member': window.categories,
},
});
});
}

Now, back in sidebar.vue, we can revert all of our changes and use the exact code we had before: const response = await fetchCategories().

And... that's it! The Promise is a little funny... because it will always resolve immediately. But that's fine! When it resolves, it will return data that's not exactly the same as an Axios response, but it's close.

Moment of truth! At our browser... it looks like it's working. Let's refresh. Yes! It definitely works. So this is a great way to get dynamic data from your server without needing an Ajax call when you really want something to be available almost instantly.

Next, let's use the categories data and currentCategoryId to print the name of the category on each page. To do that, we'll need to make sure all of that data lives in the right component.

Leave a comment!

2
Login or Register to join the conversation
Abelardo Avatar
Abelardo Avatar Abelardo | posted 2 years ago

Hi there,
A good idea to get the best experience for our users is use this component:
https://vuetifyjs.com/en/co...

It shows us a previsualization of the design of our pages as we can see at FB or other social networks.

It's a good UX trick! :) Brs.

1 Reply

Hey AbelardoLG,

Yes, agree! This is a more hipster loader screen to show "loading progress" that has a better UX than just a simple "loading" icon - It become more and more popular lately :)

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This course is also built to work with Vue 3!

What JavaScript libraries does this tutorial use?

// package.json
{
    "devDependencies": {
        "@symfony/webpack-encore": "^0.30.0", // 0.30.2
        "axios": "^0.19.2", // 0.19.2
        "bootstrap": "^4.4.1", // 4.5.0
        "core-js": "^3.0.0", // 3.6.5
        "eslint": "^6.7.2", // 6.8.0
        "eslint-config-airbnb-base": "^14.0.0", // 14.1.0
        "eslint-plugin-import": "^2.19.1", // 2.20.2
        "eslint-plugin-vue": "^6.0.1", // 6.2.2
        "regenerator-runtime": "^0.13.2", // 0.13.5
        "sass": "^1.29.0", // 1.29.0
        "sass-loader": "^8.0.0", // 8.0.2
        "vue": "^2.6.11", // 2.6.11
        "vue-loader": "^15.9.1", // 15.9.2
        "vue-template-compiler": "^2.6.11", // 2.6.11
        "webpack-notifier": "^1.6.0" // 1.8.0
    }
}
userVoice