weird behavior await running without async - javascript

I was running firebase when I saw a strange behavior which I am unable to understand. My html looks like this:
<script src="https://www.gstatic.com/firebasejs/8.2.6/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.2.6/firebase-firestore.js"></script>
<script type="module">
// Import the functions you need from the SDKs you need
import { initializeApp, } from "https://www.gstatic.com/firebasejs/9.0.2/firebase-app.js";
import { getAnalytics } from "https://www.gstatic.com/firebasejs/9.0.2/firebase-analytics.js";
import { getFirestore, collection, getDocs, addDoc } from 'https://www.gstatic.com/firebasejs/9.0.2/firebase-firestore.js';
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries
const firebaseConfig = { ... } //firebase config
// Initialize Firebase
const app = initializeApp(firebaseConfig);
const analytics = getAnalytics(app);
const db = getFirestore(app);
const querySnapshot = await getDocs(collection(db, "location")); *** //await is running without async! ***
console.log(querySnapshot)
querySnapshot.forEach((doc, index) => {
// do something with doc
*** // index always returns undefined! ***
}
Problem 1: Async running without await.
Problem 2: Index is undefined after reading and iterating data from firebase.
Please help as I studied js for more than a year and I feel like a complete newbie now.

Thank You Bergi (Profile):
It is indeed a top level await (a feature in moduleJS).
Read more about it here: https://v8.dev/features/top-level-await
Thanks! Dharamraj (Profile) => querySnapshot.docs.forEach() index works perfectly fine now.

Related

Firebase Realtime data is only available after saving the edited source file?

I'm relatively new to RN / Javascript, so this might be a rookie mistake - strangely I couldn't find anything close to it on my research.
My setup: I'm developing a RN app with Expo and I'm using the Expo Go app to see any changes.
Since I want to enable cloud services, I'm using the Firebase Realtime database with the official packages in my app. So far, so good.
My issue: Every time I start the developement server (npm start) or reload the app with the 'r' shortcut on my Accounts screen (basic screen displaying the names of the accounts the user created), see attached screenshot, the app refuses to load the data from Realtime - instead I'm greeted with a 'undefined is not an object (evaluating 'obj['accounts']'). Once I hit 'STRG + S' on my keyboard in any file opened, the Expo Go app refreshes and the data is somehow found.
If anyone could help me with this issue, you would surely save my day. :)
CODE
My data is loaded from here (dataHandler.js):
// auth stuff
import { Auth, getAuth } from "firebase/auth";
// database stuff
import { db } from "./firebase";
import { get, onValue, ref } from 'firebase/database'
// more auth stuff
const auth = getAuth()
const userID = auth.currentUser?.uid;
// database Path for retrieving data
const databasePath = userID
export var cachedData = {};
// Gets data from the firebase server, set's it to a local value
export function getData() {
return onValue(ref(db, databasePath), querySnapshot => {
let data = querySnapshot.val() || {};
let downloadedData = {...data};
// set data to a public var
cachedData = downloadedData;
console.log('DEBUG: Data loaded from the server')
})
}
My account data is then loaded from here (accountData.js):
// load the data from dataHandler.js
import { cachedData } from "./dataHandler";
import { getAuth } from "firebase/auth";
const auth = getAuth()
const userID = auth.currentUser?.uid;
export function getAccountData() {
console.log('accountData receives = ', cachedData)
let obj = cachedData[userID];
let accounts = obj['accounts'];
console.log('getAccountData returns: ', accounts)
return accounts;
}
I'm calling the files here:
// experimental stuff
import { getData } from '../../core/dataHandler';
import { getAccountData } from '../../core/accountData'
const Accounts = () => {
// Downloads data on the app start
getData();
// load the data for the account
const accounts = getAccountData()
console.log('accountData = ', accounts)
const accountKeys = Object.keys(accounts)
const [ accountName, setAccountName ] = useState('')
return( <SomeView /> )
}

Firebase JS Uncaught (in promise) TypeError: doc is not a function

I'm working on a web app using firebase and encountered this error. After a few hours of debugging, I still haven't been able to understand what's wrong, I'll leave the code here for anyone who can help me figure out what its is that I'm doing wrong. Any suggestion will be really helpful, thanks!
Code:
import {
getFirestore,
addDoc,
doc,
updateDoc,
collection,
query,
where,
getDocs,
setDoc,
} from "firebase/firestore";
const app = initializeApp(firebaseConfig);
const firestore = getFirestore(app);
var files = [....]
getDocs(collection(firestore, "property")).then((querySnapshot) => {
querySnapshot.forEach((doc) => {
if (files.includes(decrypt(doc.data().fileno))) {
if (files.includes(decrypt(doc.data().fileno), 6)) {
let owner = "ABC";
const bref = doc(collection(firestore, "property"), doc.id);
updateDoc(bref,{owner:encrypt(owner)}).then(()=>{
console.log("updated")
})
} else {
let owner = "XYZ";
const bref = doc(collection(firestore, "property"), doc.id);
updateDoc(bref,{owner:encrypt(owner)}).then(()=>{
console.log("updated")
})
}
}
});
});
You have two definitions of doc in your code:
The doc function that you import from the Firestore SDK
The doc parameter that you declare in querySnapshot.forEach((doc) => {.
The second doc hides the first one, which is why you can't call the doc function anymore inside that callback.
The solution is to give the variable a different name, like docSnapshot.

Uncaught Error: Service database is not available firebase javascript

I am trying to access my realtime database in firebase but it shows me this error Uncaught Error: Service database is not available. I have searched for what this could posabbly mean but I couldn't find anything useful or a solution.
Here is my code:
window.addPerson = addPerson;
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const userVar = urlParams.get('user')
const userVarSplitted = userVar.split('#')
const userVarFormatted = userVarSplitted[0] + ":" + userVarSplitted[1]
// Import the functions you need from the SDKs you need
import { initializeApp } from "https://www.gstatic.com/firebasejs/9.6.4/firebase-app.js";
import { getAnalytics } from "https://www.gstatic.com/firebasejs/9.6.4/firebase-analytics.js";
import { getDatabase, ref, set } from "https://www.gstatic.com/firebasejs/9.1.0/firebase-database.js";
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries
// Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {MY FIREBASE CONFIG};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
if (app.length === 0) {
console.log("no firebas app")
}else{
console.log("initialized")
}
const analytics = getAnalytics(app);
const database = getDatabase(app);
function addPerson() {
set(ref(database, "verified/" + userVarFormatted), {
name: userVarSplitted[0],
discriminator: userVarSplitted[1]
});
console.log("added")
PS: The script type is set to modular.
Do you know what the error means and what is happening?
You're using difference versions of the Firebase SDKs. I'd update the database import to version 9.6.4 too, so that the all Firebase SDK versions are the same.

Firestore (Web version 9 modular): getDocsFromCache seems not working

I'm new to Firebase and I found getDocsFromCache. I heard that firebase updated it's version to version 9, (modular) and i have to use more than just query.get({source: "cache"}). But getDocsFromCache didn't work for me. Every time i call getDocsFromCache, it does not throw any errors but snapshot.empty is always true so i can't access to my documents(collection).
If i have to cache manually, how? If not, what am i missing?
Thank you.
import {
​collection,
​getDocs,
​getDocsFromCache,
​query,
} from 'firebase/firestore';
import { db } from '../firebase-config';
export const getReviews = async () => {
​const q = query(collection(db, 'review'));
​try {
​const snapshot = await getDocsFromCache(q);
​console.log(snapshot.empty); // always true but not throwing any error
​snapshot.forEach((doc) => {
​/* ... */
​});
​} catch (e) {
// never reach here
​const snapshot = await getDocs(q);
/* ... */
​}
};
From the documentation on configuring offline persistence:
For the web, offline persistence is disabled by default. To enable persistence, call the enablePersistence method.
So make sure to enable the cache by calling this right after initializing Firebase and Firestore:
import { enableIndexedDbPersistence } from "firebase/firestore";
enableIndexedDbPersistence(db);

Setting Secrets from AWS Secrets manager in Node.JS

Before top-level await becomes a thing, loading secrets asynchronously from AWS Secrets Manager upon startup is a bit of a pain. I'm wondering if anyone has a better solution than what I currently have.
Upon starting up my Node.JS server I'm loading all secrets from AWS Secrets manager and setting them in config files where I have a mix of hardcoded variables and secrets. Here's an example:
In aws.js
import AWS from 'aws-sdk';
const region = "eu-north-1";
AWS.config.setPromisesDependency();
const client = new AWS.SecretsManager({
region
});
export const getSecret = async(secretName) => {
const data = await client.getSecretValue({SecretId: secretName}).promise();
return data.SecretString;
}
Then in sendgridConfig.js
import { getSecret } from "./aws";
export default async() => {
const secret = JSON.parse(await getSecret("sendgridSecret"));
return {
APIKey: secret.sendgridKey,
fromEmail: "some#email.com",
toEmail: "some#email.com"
}
}
Then in some file where the config is used:
import { sendgridConfig } from "./sendgridConfig";
const myFunc = () => {
const sendgridConf = await sendgridConfig();
... do stuff with config ...
}
This works okay in async functions, but what if I'd like to use the same setup in non-async functions where I use my hardcoded variables? Then the secrets haven't been fetched yet, and I can't use them. Also I have to always await the secrets. IMO a good solution in the future could be top level await, where upon booting the server, the server will await the secrets from AWS before proceeding. I guess I could find a way to block the main thread and set the secrets, but that feels kind of hacky.
Does anyone have a better solution?
So I ended up doing the following. First I'm setting the non-async config variables in an exported object literal. Then I'm assigning values to the object literal in the sendgridConfigAsync IIFE (doesn't have to be an IFEE). That way I don't have to await the config promise. As long as the app awaits the IIFE on startup, the keys will be assigned before being accessed.
In sendgridConfig.js
import { getSecret } from "./aws";
export const sendgridConfig = {
emailFrom: process.env.sendgridFromEmail,
emailTo: process.env.sendgridToEmail
}
export const sendgridConfigAsync = (async() => {
const secret = JSON.parse(await getSecret("Sendgrid-dev"));
sendgridConfig.sendgridKey = secret.key;
})()
Then in the main config file _index.js where I import all the config files.
import { sendgridConfigAsync } from "./sendgrid";
import { twilioConfigAsync } from "./twilio";
import { appConfigAsync } from "./app";
export const setAsyncConfig = async() => {
await Promise.all([
appConfigAsync,
sendgridConfigAsync,
twilioConfigAsync
]);
}
Then in the main index.js file I'm awaiting the setAsyncConfig function first. I did also rebuild the app somewhat in order to control all function invocations and promise resolving in the desired order.
import { servicesConnect } from "../src/service/_index.js";
import { setAsyncConfig } from '$config';
import { asyncHandler } from "./middleware/async";
import { middleware } from "./middleware/_index";
import { initJobs } from "./jobs/_index"
import http from 'http';
async function startServer() {
await setAsyncConfig();
await servicesConnect();
await initJobs();
app.use(middleware);
app.server = http.createServer(app);
app.server.listen(appConfig.port);
console.log(`Started on port ${app.server.address().port}`);
}
asyncHandler(startServer());
Yup I have the same problem. Once you start with a promise, all dependencies down the line require await.
One solution is to do your awaits and only after that have all your downstream code run. Requires a slightly different software architecture.
E.g.
export const getSecretAndThenDoStuff = async(secretName) => {
const data = await client.getSecretValue({SecretId: secretName}).promise();
// instead of return data.SecretString;
runYourCodeThatNeedsSecret(data);
}
A more generic solution to the top-level await that I tend to use:
async function main() {
// Do whatever you want with await here
}
main();
Clean and simple.

Categories

Resources