Returning undefined object from a firebase snapshot - firebase

I implemented a function where I want to return an object saved under a certain url. In the code below, the first 'console.log(result);' returns the right object from the firebase location. The second one return undefined. Can somebody explain why and how to fix it?
_getById: function(obj) {
var url = "https://my-app.firebaseio.com/list/" + obj.groupId;
console.log(url);
var ref = new Firebase(url);
var result = {};
ref.on("value", function(snapshot) {
result = snapshot.val(); //first
console.log(result);
}, function (errorObject) {
}
);
console.log(result); //second
return result;
},

The data is loaded from Firebase asynchronously. So you'll notice that your second console.log() displays before the first one. You cannot return data that is being loaded asynchronously.
You'll have to change the way you code. Instead of "get the id, then do something with it", you need to "do something whenever the id is loaded/changed".
So instead of:
var list = _getById({ groupId: 42});
console.log("Our list is: "+list);
You'll:
_getById({ groupId: 42 }, function(list) {
console.log("Our list is: "+list);
});
_getById: function(obj, callback) {
var url = "https://my-app.firebaseio.com/list/" + obj.groupId;
console.log(url);
var ref = new Firebase(url);
var result = {};
ref.on("value", function(snapshot) {
result = snapshot.val(); //first
callback(result);
}, function (errorObject) {
});
console.log(result); //second
return result;
},
In the above code we're passing a callback into _getById() and invoke that callback when the list has loaded (and whenever the list changes).
Some further reading material:
Polymer Firebase: Print async data
Asynchronous access to an array in Firebase
Trying to get child records from Firebase
Handling Asynchronous Calls (Firebase) in functions

Related

Why is the asnyc/await function not been waited?

I am doing an Image Upload feature with Cloudinary. I'm providing an array which may contains base64coded or uploaded image which is a url :
[
"https://res.cloudinary.com/\[userName\]/image/upload/v167xxxx4/luxxxfsgasxxxxxx7t9.jpg", "https://res.cloudinary.com/doeejabc9/image/upload/v1675361225/rf6adyht6jfx10vuzjva.jpg",
"data:image/jpeg;base64,/9j/4AAUSkZJRgABAQEBLAEsAA.......", "data:image/jpeg;base64,/9j/4AAUSkZJRgABAQEBLAEsAA......."
]
I'm using this function to upload the "un-uploaded", which returns the all uploaded version:
export async function uploadImage(el: string[]) {
const partition = el.reduce(
(result: string[][], element: string) => {
element.includes("data:image/")
? result[0].push(element)
: result[1].push(element);
return result;
},
[[], []]
);
for (let i = 0; i < partition[0].length; i++) {
const data = new FormData();
data.append("file", partition[0][i]);
data.append("upload_preset", "my_preset_name");
const res = await fetch(
"https://api.cloudinary.com/v1_1/userName/image/upload",
{
method: "POST",
body: data,
}
);
const file = await res.json();
partition[1].push(file.secure_url);
console.log(partition[1]);
}
return partition[1];
}
Then I will use the return value to update the state and call the api to update database:
const uploaded = await uploadImage(el[1])
console.log(uploaded);
setFinalVersionDoc({
...chosenDocument,
[chosenDocument[el[0]]]: uploaded,
});
However, it always updates the useState before the console.log(uploaded). I thought async/await would make sure the value is updated before moving on.
The GitHub repo is attached for better picture. The fragment is under EditModal in the 'component/document' folder:
https://github.com/anthonychan1211/cms
Thanks a lot!
I am hoping to make the upload happen before updating the state.
The function is correct, but you are trying to await the promise inside the callback function of a forEach, but await inside forEach doesn't work.
This doesn't work:
async function handleEdit() {
const entries = Object.entries(chosenDocument);
entries.forEach(async (el) => { // <------ the problem
if (Array.isArray(el[1])) {
const uploaded = await uploadImage(el[1]);
el[1].splice(0, el[1].length, uploaded);
}
});
[...]
}
If you want to have the same behaviour (forEach runs sequentially), you can use a for const of loop instead.
This works (sequentially)
(execution order guaranteed)
async function handleEdit() {
const entries = Object.entries(chosenDocument);
for (const el of entries) {
// await the promises 1,2,...,n in sequence
if (Array.isArray(el[1])) {
const uploaded = await uploadImage(el[1]);
el[1].splice(0, el[1].length, uploaded);
}
}
}
This also works (in parallel)
(execution order not guaranteed)
async function handleEdit() {
const entries = Object.entries(chosenDocument);
await Promise.all(entries.map(async (el) => {
// map returns an array of promises, and await Promise.all() then executes them all at the same time
if (Array.isArray(el[1])) {
const uploaded = await uploadImage(el[1]);
el[1].splice(0, el[1].length, uploaded);
}
}));
[...]
}
If the order in which your files are uploaded doesn't matter, picking the parallel method will be faster/better.

Ionic + Firebase unable return a promise with fetched data from DB

I'm new to Firebase and Ionic and currently trying to get some data from the server as a certain view is loaded. Right now I am able to get the data inside the service, but I'm unable to return the fetched data as a promise to a controller. This is the code that calls the service:
$scope.$on('$ionicView.beforeEnter', function () {
var user = firebase.auth().currentUser;
fireBaseService.getDataFromDB(user.uid).then(function(res){
console.log(res);
});
And here is the service that calls the Firebase API:
this.getDataFromDB = function(uid){
return firebase.database().ref('table/' + uid).on('value', function(snapshot) {
var data = snapshot.val(); //data is available here
return data;
});
}
This is the error I'm getting:
TypeError: fireBaseService.getExpensesFromDB(...).then is not a function
call from controller
fireBaseService.getDataFromDB(user.uid, function(res) {
console.log(res);
}
service call
getDataFromDB = function(uid, callback){
return firebase.database().ref('table/' + uid).on('value', function(snapshot) {
var data = snapshot.val(); //data is available here
callback(data);
});
}

How to call yield in firebase read API

I have a redux saga API, where by I am connecting to firebase and reading records of data.
var roomRef = firebase.database().ref('/Users/' + userid + '/rooms')
var rooms = []
roomRef.once('value', function (snap) {
var roomkeys = snap.val()
for (var roomkey in roomkeys) {
firebase.database().ref('/Rooms/' + roomkey).once('value', function (item) {
rooms.push(item.val())
})
}
console.log(rooms)
--> put({type: 'LOAD_ROOMS', payload: { rooms: rooms}})
})
Since my put is inside a callback function I cannot use the yield keyword. How do I dispatch an event to change the state of my reducer with the new values 'rooms'
The way to work around this is to convert the callback into a promise. redux-saga knows how to resolve promises that you pass to the call effect. But call takes a function, not a promise. From the docs:
If the result is a Promise, the middleware will suspend the Generator until the Promise is resolved, in which case the Generator is resumed with the resolved value. or until the Promise is rejected, in which case an error is thrown inside the Generator.
var roomRef = firebase.database().ref('/Users/' + userid + '/rooms')
var rooms = yield call(function() {
return new Promise(function(resolve, reject) {
roomRef.once('value', function (snap) {
var rooms = []
var roomkeys = snap.val()
for (var roomkey in roomkeys) {
firebase.database().ref('/Rooms/' + roomkey).once('value', function (item) {
rooms.push(item.val())
})
}
resolve(rooms)
})
})
})
yield put({type: 'LOAD_ROOMS', payload: { rooms: rooms}})

Correct usage of Meteor wrapAsync

The following is what I am trying to do:
var joinNetwork = function (obj) {
Meteor.call("joinNetwork", {
userId: obj.userId,
domain: obj.domain
}, function (err, networkId) {
return networkId;
});
}
Accounts.onCreateUser(function (options, user) {
var userId = user._id;
var email = options.email;
var domain = Utils.getDomain(email);
var joinNetworkSync = Meteor.wrapAsync(joinNetwork);
// works fine until here
var networkId = joinNetworkSync({
userId: userId,
domain: domain
});
// never get here
debugger
As you can see, after I call joinNetworkSync I never reach the code after it. In other words, networkId is never available. What am I doing wrong?
To return from a wrapAsync you have to call a callback passed to that function:
Meteor.wrapAsync(function (obj, done) {
Meteor.call("joinNetwork", {
userId: obj.userId,
domain: obj.domain
}, function (err, networkId) {
done(networkId);
});
})
You don't need wrap async here dough. When you call meteor methods server side, they return like normal functions. You can just do this if the joinNetwork method is properly defined:
Accounts.onCreateUser(function (options, user) {
var userId = user._id;
var email = options.email;
var domain = Utils.getDomain(email);
var networkId = Meteor.call("joinNetwork", {
userId: obj.userId,
domain: obj.domain
});
...
})
I think your sync version of joinNetwork is not returning anything. You placed a return inside another function, the callback of joinNetwork. Try splitting the next part up in another function and call that inside the callback function using the networkId.

using Future.wait() in Meteor.methods

Running subscriptions in async mode with new Future() works fine but if the same thing is done in a Meteor.method the application crashes with a message that it can not wait without a fiber. But I have to return something from the Meteor.method
This is the method:
/**global variables*/
var Fiber = Npm.require("fibers");
var Future = Npm.require("fibers/future");
Meteor.methods({
'single-data': function (form, state, tenant, selectedRow, search, sort) {
var user = Meteor.users.findOne({_id: this.userId});
/**check if the user can see the data in the state*/
if (!(isAdmin(user, form) || hasPermission(user, form, state, "read"))) {
return null;
}
/** set the sort order for the query*/
var order = [];
if (sort) {
order.push(["fieldData." + sort.column + ".0.value", sort.order == 1 ? "asc" : "desc"]);
}
order.push(["submitTime", "desc"]);
var query = dataQuery(form, state, _.uniq(_.pluck(user.groups, "tenant")), search);
var fut = new Future();
var fut = new Future(); setTimeout(function () {
fut.ret(FormDatas.find(query, {sort: order, skip: selectedRow, limit: 1}).fetch()[0]);
}, 0 * 1000);
// Wait for async to finish before returning the result
return fut.wait();
}
});
I believe that the return value from future should not contain asynchronous code, it should happen before calling ret(). Anyway, in your case future is not needed, simply call
return FormDatas.findOne(query, options);

Resources