Understand Vue and Return and Async Await - javascript

I am new to Javascript and Vue and I am having a hard time wrapping my head around return, async and await work together. I come from a Python background and JS syntax is quite different
So... some background to the problem....I'm building a Vue blog and creating multiple Vuex stores using module mode. I am also creating a function to retrieve data from Prismic.
./store/blog.js
import {MyFunctions} from "../plugins/myfunctions.js";
export const actions = {
async retrievePosts() {
console.log("HELLO")
return MyFunctions.MyFunction("blog_post");
}
}
./plugins/myfunctions.js
import Prismic from "prismic-javascript";
import PrismicDom from "prismic-dom" //importing the Dom
import PrismicConfig from "../prismic.config.js";
export const MyFunctions = {
MyFunction: async function (doctype) {
console.log("Before")
const api = await Prismic.getApi(PrismicConfig.apiEndpoint)
let blog_post = {}
const results = await api.query(
Prismic.Predicates.at("document.type", doctype),
{ lang: "en-us" } //This is a Prismic query option
)
console.log("After")
result = results.results
return result;
}
};
Heres my question:
In blog.js, if I remove the word "return" in front of MyFunctions.MyFunction("blog_post") , the await in myfunctions.js do not get activated. Why?
I say this because without the return, "Before" is console logged but "After" is not and nothing is returned.
A further question for my enlightenment:
Where does return MyFunctions.MyFunction("blog_post") return to? So the data is returned to retrievePosts() but it is part of a {} which is a javascript object I believe? So does it become like a property inside {}?

Ans 1:You are actually returning a function to the caller of retrievePosts() in async mode. By removing return, function will be executed but it don't make any effect to the caller of retrievePosts() function.
Ans 2:The MyFunctions.MyFunction("blog_post") is returned to the caller of retrievePosts() function.

Related

Declarations in script setup return undefined variables after async call with top level await

My Nuxt 3 / Vue 3 component with script setup looks like:
<script setup>
const content = await useFetch('/api/items/published').then((res) => res.data)
const items = await content.data.items
const issues = await content.data.issues
</script>
<template>
<Gallery :items="items" :layout="layout" #sponsorModalOpen="sponsorModalOpen()" />
</template>
The variables items and issues are returned as Undefined presumably because they are not awaiting the result of the first async call with useFetch before being set. This seems unintuitive as I would have expected the await on both the useFetch operation and the variable declarations to block until the pre-requisite task has finished.
If I re-write the code to pass content directly to the Gallery component as below, it works:
<Gallery :items="content?.items" :layout="layout" #sponsorModalOpen="sponsorModalOpen()" />
though notable this only works with the optional chaining as it is presumably rendering initially with the prop undefined, then updating once the async fetch operation completes.
Am I using await wrong here? How can I ensure that the variables aren't set until the async operation completes so that I can manipulate them (specifically to re-order the results)?
The only solution I have found to this so far is to do the sorting directly in the then() callback on the initial fetch as below:
const content = await useFetch('/api/items/published').then((res) => {
const sortedItems = res.data.value.items.sort((a, b) => {
return b.issueNumber - a.issueNumber
});
console.log(res.data.value.issues)
const sortedIssues = res.data.value.issues.sort((a, b) => {
const dateA = new Date(a.publishDate)
const dateB = new Date(b.publishDate)
console.log(dateA, dateB)
return dateB - dateA
});
return {
sortedItems: sortedItems,
sortedIssues: sortedIssues
}
})
It may be that using Suspense would also be a solution, but I have not yet looked into this.

Reexecuting await/async/promise.all function with a new value

I am basically trying to send multiple API queries at once and wait for the result. The code works well for the first query, but I am actually struggling to reset the value of the prior async return. After the first execution is completed, it keeps returning the very first values. The goal is to assign random href(URL) with every function's execution and recall the API once again, so it can call the API once again with a newly randomized href and return new values. Here is the code, would be great is someone could help:
async function fetchData() {
const [test0_array_response,
test1_array_response,
test2_array_response,
] = await Promise.all([
fetch(random_href+'/test0/'),
fetch(random_href+'/test1/'),
fetch(random_href+'/test2/'),
]);
const test0_array = await test0_array_response.json();
const test1_array= await test1_array_response.json();
const test2_array = await test2_array_response.json();
return [test0_array, test1_array, test2_array];
}
function callApi() {
console.log(fetchData()) // returns a promise
};
From what I can see random_href is part of the closure of fetchData, so you need to pass it as argument inside callApi or wherever you use fetchData in order to use his correct value.
Take a look here for learn what I means with closure.
You should do something like this:
async function fetchData(random_href) {
const [test0_array_response,
test1_array_response,
test2_array_response,
] = await Promise.all([
fetch(random_href+'/test0/'),
fetch(random_href+'/test1/'),
fetch(random_href+'/test2/'),
]);
const test0_array = await test0_array_response.json();
const test1_array= await test1_array_response.json();
const test2_array = await test2_array_response.json();
return [test0_array, test1_array, test2_array];
}
function callApi(random_href) {
console.log(fetchData(random_href)) // returns a promise
};
Another approach could be to make an object random_container = { href: 'VALUE'} and use it's reference inside the fetchData function. So, for example, fetch(random_container.href+'/test0/'). In this way random_container will be part of the closure of fetchData but you'll be able to read the last value of random_container.href.

How to make an entire script wait for response from Firebase?

I'm getting some data from Firebase and exporting this data to another script as an object, but since script-2.js is executed before the response in the script-1.js gets returned, I'm not able to get the data in time. I'm fairly new with both Firebase and promises and I'm having a hard time with them right now.
My project is something like this:
SCRIPT 1 (fetching data and storing it into fetchedData):
let fetchedData= {}
const db = firebase.firestore()
const getTechs = () => {
return db.collection('techs').get()
}
window.addEventListener('DOMContentLoaded', async e => {
const querySnapshot = await getTechs()
querySnapshot.forEach(doc => {
const key = Object.keys(doc.data())
const value = Object.values(doc.data())
fetchedData[key] = value
})
})
export default fetchedData
SCRIPT 2 (Importing fetchedData from script-1.js
import fetchedData from './script-1.js'
console.log(fetchedData) // => here I have an empty object, since it's not populated yet.
//...do something with the data
If I was fetching the data and using it in the same script I could make this work, but I'm going to use fetchedData on multiple other scripts, so it would be nice not to have to fetch it on all of them.
How could I solve this?
P.S. I already tried with a hard coded object in script-1.js and worked fine, the problem really is what I described.
You can export a Promise which would (eventually) resolve to your fetched data:
/* script 1 */
let resolve;
const promise = new Promise(r => resolve = r);
// eventually
resolve(data);
export default promise;
/* script 2 */
import fetchedDataPromise from './script-1.js';
// whenever you need it
const fetchedData = await fetchedDataPromise;
Alternatively, you could try to delay the execution of the 2nd script. Depending on how/when that gets executed, that might be possible.

js only - run a function only once

I'm fetching some data from firebase and would like to run async/await function (to fetch data) only once upon the first page load. I'm used to React and lifecycle methods / hooks doing it but this little project is just too small to use React. I just need to run this function once, fetch the data, save it to a variable and do not make any further calls to firebase api in the same session.
async function getEntries() {
const snapshot = await firebase.firestore().collection('riders').get()
// Do my thing with the data, etc.
// console.log(snapshot.docs.map(doc => doc.data()));
}
Is there any js-only way of running this function only once when the page loads?
If you call a function just once, why do you need the function at all?
const snapshot = await firebase.firestore().collection('riders').get()
// Do my thing with the data, etc.
// console.log(snapshot.docs.map(doc => doc.data()));
This top level await only works in modules, and it blocks all depending modules to load. If that is not necessary (they don't depend on the data), or if you don't want write a module, you can wrap the code in an async IIFE, and store the returned promise in a variable:
const dataPromise = (async function() {
//...
return data;
})();
While the data is loading, you might want to show some loading icon or so. That can easily be done with the following hook:
function usePromise(p) {
const [state, setState] = useState(null);
useEffect(() => { p.then(setState); }, []);
return state;
}
// Inside a component:
const data = usePromise(dataPromise);
if(data === null)
return <Loading />;
// show data
Yes. You can use Self Invoking (self executing) Functions. Syntax is like:
(function(){})();
The last parentheses are for running function. the function is anonymous.
You can Implement it this way:
(async function () {
const snapshot = await firebase.firestore().collection('riders').get()
})();
in this way you can never call this function again and it will run only once.
Tutorial: https://blog.mgechev.com/2012/08/29/self-invoking-functions-in-javascript-or-immediately-invoked-function-expression/
And The question you asked is somehow duplicate and answered here: Function in JavaScript that can be called only once
What you are looking for is memoization of the function result. There are several libraries to supporting including react.
Theres also a handmade pattern you can use by changing the function implementation after it's called once, accoring to JavaScript: The Good Parts
async function getEntries() {
const snapshot = await firebase.firestore().collection('riders').get()
// Do my thing with the data, etc.
// console.log(snapshot.docs.map(doc => doc.data()));
getEntries = async function(){
return snapshot
}
return snapshot
}
I think you can load it with the load method when the page is first loaded and then set it to cookie or local stroge. You can check this value on next page loads. You can do this quickly using jQuery.
$(window).load(function() {
var item = localStorage.getItem('test');
if(item != null){
// your code
}
else {
localStorage.setItem('test', 1);
}
});
The simplest way is to make a global variable like:
let isCalled = false;
and in the function body do:
if(isCalled) return;
//the stuff the function would do
isCalled = true;
//Assign isCalled to true before using a return statement as it will make the program discard the lines below it.

Fetch Data from WatermelonDB in React

I am using WatermelonDB as a local database of my app.
I have a method getPlaces() inside a class AccessDB:
static async getPlaces() {
const postsCollection = database.collections.get('places');
const allPosts = await postsCollection.query().fetch();
return allPosts;
}
Calling getPlaces() using AccessDB.getPlaces() with async and await works. How can I fetch the results matching the query?
The variable allPosts is an array with all places in your case. So to access the properties of the places, you would do in a different class:
import AccessDB from './AccessDB';
and then somewhere for example
(async () => {
try {
const places = await AccessDB.getPlaces();
var buffer = '';
for (place in places) {
buffer += places[place].name + '\n'
}
console.log(buffer)
} catch(e) {console.log(e)}
})()
You have to be carefully when debugging with console.log, as
1) console.log skips outputs if executed fast one after the other, e.g. if you console.log the places[place].name every time in the for-loop above and
2) if you console.log(places) you see the same as if you console.log(database) which is very confusing. You would expect to see [Object object],[Object object]..., which you see when you use alert(places).
Maybe I am not understanding fully. But did you try to put query inside your query method
const allPosts = await postsCollection.query(Q.where('is_verified', true)).fetch();
Also do not forget that getPlaces is async and your need async\await to get return data.

Categories