Angular firebase fetching single item has race conditions - angularjs

I'm using angular and firebase together and I have a products array which i'm storing in my rootscope, though it takes time to load the items.
My issues is that when I go to this page for example directly:
http://localhost/product/greyish-sports-shoes
If I go to the home page, the products load after 2 seconds.. and then only if I click on the product link it takes me to it, and it'll work because products have already been loaded.
It goes to the shoeService which contains the products array, but the items are still not loaded, so it cannot find the product by its slug.
That's the code I use in my run method.
var ref = firebase.database().ref().child('products');
$rootScope.shopProds = $firebaseArray(ref);
My shoeService factory:
function shoeFactory($rootScope) {
this.service = {};
this.service.store = new Store($rootScope.shopProds);
this.service.cart = new Cart();
return this.service;
}

It is important to realize that the $firebaseArray service returns an array that is initially empty. The array is populated asynchronously after the data is returned from the server.
Use the promise returned by the $loaded method attached to the array:
function shoeFactory($rootScope) {
this.service = {};
this.service.storePromise = $rootScope.shopProds.$loaded()
.then ( (shopProds) => {
return new Store(shopProds);
});
this.service.cartPromise = this.service.storePromise
.then ( () => {
return new Cart();
}).catch( (error) => {
console.log("ERROR in shoeFactory");
throw error;
});
return this.service;
}
To avoid race conditions, the code needs to use promises to chain operations.

Related

React: Method finishing before data loaded

I am trying to retrieve some data from Yahoo Finance using an XHTML Request, which works. However, I am trying to display the data retrieved on my app, but the method to retrieve the data is returning "undefined" before the data has been loaded.
async componentDidMount() {
var tempData = await this.fetchAsync();
console.log(tempData)
this.handleLoad(tempData)
}
handleLoad = (num) => {
this.setState(state => ({
price: num
}));
}
async fetchAsync () {
const xhr = new XMLHttpRequest();
xhr.withCredentials = true;
const {params} = this.props.navigation.state;
var ticker = params.ticker;
var result;
var tempArray = [1];
var url = "https://yahoo-finance-low-latency.p.rapidapi.com/v8/finance/spark?symbols=" + ticker + "&range=2y&interval=1d"
xhr.addEventListener("readystatechange", function () {
if (this.readyState === this.DONE) {
result = JSON.parse(this.responseText);
tempArray = result[ticker]['close'];
testPrice = tempArray[tempArray.length-1]
console.log(testPrice)
var self = this;
return tempArray[tempArray.length-1]
}
});
xhr.open('get', url, true);
xhr.setRequestHeader("x-rapidapi-key", "my key");
xhr.setRequestHeader("x-rapidapi-host", "yahoo-finance-low-latency.p.rapidapi.com");
xhr.send();
}
I am using the componentDidMount() function to begin calling the methods to load the data, but when the app renders, the values are not displayed.
As you can see inside the fetchAsync() method, I return the value I need, but when I try and console.log the return from this method, I get undefined.
I have also tried moving this return to the end of the method, but when I use console.log here to ensure that tempArray has the data I need, it is empty.
I need to display tempArray[tempArray.length-1] on my screen, but the data is not loaded in time, and does not update even after it has loaded.
Your return tempArray[tempArray.length-1] inside the fetchAsync isn't actually returning from fetchAsync -- it's just returning from the callback function inside addEventListener. In fact, you don't actually have any code that is taking advantage of the async tag you have on that function.
One solution to this would be to call handleLoad directly from inside fetchAsync instead of return tempArray. (Of course, you'll want to make sure that you've bound this correctly to handleLoad).
Another solution would be to pass a callback function into fetchAsync that you could call instead of returning. Then, at your call site, it might look something like this:
this.fetchAsync((tempData) => {
console.log(tempData)
this.handleLoad(tempData)
});
Finally, a third solution would be to switch from XMLHTTPRequest to fetch, and then you could take advantage of async/await and actually make that fetchAsync method async (and be able to return a value from it).

Exporting an array within an ".then" doesnt work

I'm new to NodeJS and are only familiar with Java. I'm trying to create a file that creates objects based on a database and adds them to an array. This array I want to be able to export so that I can use it throughout the whole program, but when I try to export the array it doesn't work. I've tried googling and understanding but haven't come across anything that was helpful unfortunately.
I hope that someone can help me understand
I've tried calling module.exports after the ".then" call, but it just returns an empty array because its async.
I've also tried calling module.exports = teams inside the .then call but it didn't work neither.
var teams = [];
function assignTeamsToClasses() {
return new Promise((resolve, reject) => {
getAllTeamsInDb((teamList) => {
teamList.forEach((aTeam) => {
let newTeam = new Team(aTeam['teamid'], aTeam['teamname'], aTeam['teamrank']);
teams.push(newTeam);
});
resolve();
});
})
}
assignTeamsToClasses().then(() => {
module.exports = teams;
});
main.js
var teams = require('./initialize.js');
console.log(teams);
I expect it to return all teams that are in the database. I know that array is not empty when called within the ".then" call, but the export part does not.
Simple
the sequence require() + console.log() is synchronous
assignTeamsToClasses() is asynchronous, i.e. it updates teams at some unknown later point in time.
You'll have to design your module API to be asynchronous, e.g. by providing event listener interface or Promise interface that clients can subscribe to, to receive the "database update complete" event.
A proposal:
module.exports = {
completed: new Promise(resolve =>
getAllTeamsInDb(teams => {
const result = [];
teams.each(aTeam =>
result.append(new Team(aTeam.teamid,
aTeam.teamname,
aTeam.teamrank)
)
);
resolve(result);
})
),
};
How to use it:
const dbAPI = require('./initialize.js');
dbAPI
.completed
.then(teams => console.log(teams))
.catch(error => /* handle DB error here? */);
Every caller who uses this API will
either be blocked until the database access has been completed, or
receive result from the already resolved promise and proceed with its then() callback.

How to call the $firebaseArray in the cloud function of firebase

So in my angular JS web app, I have a function that calls on a node in the firebase database called orderedPlayers and returns it as an array as follows:
$firebaseArray(orderedPlayers)
.$loaded(function(loadedPlayers) {
// function in here
});
When attempting to do something similar in the cloud function I am experiencing problems. Is there a way to return the the players node as an array?
I know i can access the database as follows:
admin.database().ref('orderedPlayers');
but the $firebaseArray doesnt work.
These docs can help: https://firebase.google.com/docs/database/admin/retrieve-data
snapshot.val() will return an object that can be referenced as a key-value array. In your case:
admin.database().ref('orderedPlayers').on("value", function(snapshot) {
var loadedPlayers = snapshot.val();
//access your players here
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
});
To the best of my knowledge cloudfunctions does not include firebaseArray. You can instead just make the players children into an array.
so:
let yourArray = [];
admin.database().ref('orderedPlayers').once('value').then(snap => {
snap.forEach(childSnap => {
yourArray.push(childSnap));
});
});
or you can use the children:
let yourArray = [];
admin.database().ref('orderedPlayers').on('child_added',snap => {
yourArray.push(snap);
});
*on('child_added') will always be called at least once so it removes the need to loop over the children.
If you want to query the database by a specific value you just add something like orderByChild('order') after the ref and before calling once or on

Why is my service running before anything else?

I'm new to services, factories, etc; so there's still a lot I don't understand.
I have a ui-grid. When a row is selected, I want to use that data as a param in a service to get REST data to populate another grid.
This is what I think it should be doing:
gridOne is registered => Row Selected => Send selectedRow.id to Service => Service GETs data => data populates grid 2
This is what its actually doing:
Service GETs data => Error because selectedRow.id is not defined.
01| $scope.gridOne.onRegisterApi = function(gridApi){
02| $scope.gridOneApi = gridApi
03| $scope.gridOneApi.selection.on.rowSelectionChanged(null, function(row){
04| $scope.gridOneSelectedRow = $scope.gridOneApi.selection.getSelectedRows()[0]
05|
06| // v---Breakpoint on this line triggered before any grid is built---v
07| myService.getAllObjects($scope.gridOneSelectedRow.id).then(response => {
08| $scope.grid2.data = response
09| }
10| })
11| }
My service looks like this:
app.service('myService', function ($http) {
return {
get: getObjects
}
function getOjects(id) {
let url = `http://${domain}/object/${id}`
return $http.get(url).then(response => {
return response
}).catch(error => {
return error
})
}
}
Why is the service function running before everything else?
if you are writing a service, you should not return a object/function/something, your implementation function is used as a constructor to newed up and create instance of your service.
so if you want to use a service, a sample for your myService will be
app.service('myService', function ($http) {
function getOjects(id) {
//you should properly replace your domain, may be with the same domain by default if you start from object/...
let url = `http://${domain}/object/+ id`
return $http.get(url).then(response => {
return response.data
}).catch(error => {
return error
})
}
this.getAllObjects = getOjects;
})
in case of factory
app.factory('myService', function ($http) {
function getOjects(id) {
//you should properly replace your domain, may be with the same domain by default if you start from object/...
let url = `http://${domain}/object/+ id`
return $http.get(url).then(response => {
return response.data
}).catch(error => {
return error
})
}
return {getAllObjects: getOjects};
})
and in the injecting end you don't need to change the code the way you are using it to load data, just wrote the code in sync of use
and also, I wnder, why you are trying to select before the data is loaded, and inside the handler of row selection you want to call the data when now row is present at all, if the data is not loaded, right? I hope you are loading data for another grid grid2 on selection of a row of grid1, and then want to load corresponding data related to that row to grid2.
Feel free to comment, whatever doubt you still have.
Good Luck :)

How to roll back changes when there is an error in a promise chain

In my angular app I want to make changes to several locations in my firebase with a mix of transactions and set. I have written a promise chain with a little help. Now I need to handle any errors that may occur.
In the event of an error on any of the promises I would want to roll back any changes made in firebase (the successful promises) and alert the user to the failure.
Current code below
$scope.addNewPost = function() {
var refPosts = new Firebase(FBURL).child('/posts').push();
// Get tags into array for incrementing counters
var tags = $scope.post.tags.split(', ');
var allPromises = [];
// Iterate through tags and set promises for transactions to increment tag count
angular.forEach(tags, function(value, index){
var dfd = $q.defer();
var refTag = new Firebase(FBURL).child('/tags/' + value);
refTag.transaction( function (current_value) {
return current_value + 1;
}, function(error, committed, snapshot) {
if (committed) {
dfd.resolve( snapshot );
} else {
dfd.reject( error );
}
});
allPromises.push( dfd.promise );
});
// Add promise for setting the post data
var dfd = $q.defer();
refPosts.set( $scope.post, function (error) {
if (error) {
dfd.reject(error);
} else {
dfd.resolve('post recorded');
}
});
allPromises.push( dfd.promise );
$q.all( allPromises ).then(
function () {
$scope.reset(); // or redirect to post
},
function (error) {
// error handling goes here how would I
// roll back any data written to firebase
alert('Error: something went wrong your post has not been created.');
}
);
};
So what I need to know is how do I roll back any changes that happen to my firebase data in the event that one of these promises fail. There could be any number of updates happening in firebase. (for example: 3 tags being incremented via transaction and the post data being set)
How would I write the failure function to calculate what was successful and undo it? If this is this even possible.
--------------- sub question from original post has been solved ---------------
Also how do you force errors? I've tried setting a variable like below but it doesn't seem to work, is there something wrong with my .then?
refPosts.set( $scope.post, function (error) {
var forceError = true;
if (forceError) {
dfd.reject(forceError);
} else {
dfd.resolve('post recorded');
}
allPromises.push( dfd.promise );
});
There are two instances of this line, and they are both in the wrong place:
allPromises.push( dfd.promise );
In the first block, it should be in the last statement in the forEach callback, not in the transaction callback.
In the second block, it should be after the call to set(), not in the callback.
The way your code is written now, $q.all() is getting an empty array of promises. That could also be what's interfering with the forceError test you're attempting.

Resources