Atomically incrementing a shared counter in Firebase realtime database - javascript

I need to decrement a counter (named credits) stored in Firebase real time database.
To decrement the counter I do like this:
var ref = admin.database().ref('licenseCredits/' + name + '/credits');
ref.transaction( (value) => {
if (value === null) {
return 0;
} else if (typeof value === 'number') {
return value - 1;
} else {
console.log('The counter has a non-numeric value: ');
}
});
The credits field is being correctly decremented.
I put this code into a callable function but I don't know how to return the decremented value to the caller. If I simply return the ref.transaction result I get a "Unhandled RangeError exception".

As per the documentation below, the there should be a onComplete function implemented.
https://firebase.google.com/docs/reference/js/firebase.database.Reference#transaction
var ref = admin.database().ref('licenseCredits/' + name + '/credits');
ref.transaction( (value) => {
if (value === null) {
return 0;
} else if (typeof value === 'number') {
return value - 1;
} else {
console.log('The counter has a non-numeric value: ');
}
}, function(error, committed, snapshot) {
if (error) {
console.log('Transaction failed abnormally!', error);
} else if (!committed) {
console.log('We aborted the transaction.');
} else {
console.log('Success!');
}
console.log("Credit data: ", snapshot.val());
});

At the end I found a way to tackle the problem taking into account #chris answer.
I used a javascript promise implemented using the 'kew' library.
Here is the working code:
var qTrans = Q.defer();
var ref = admin.database().ref('licenseCredits/' + name + '/credits');
var credits = 0;
ref.transaction( (value) => {
if (value === null) {
// the counter doesn't exist yet, start at one
return 1;
} else if (typeof value === 'number') {
// increment - the normal case
return value + 1;
} else {
// we can't increment non-numeric values
console.log('The counter has a non-numeric value: ' + JSON.stringify(value));
// letting the callback return undefined cancels the transaction
}
}, (error, committed, snapshot) => {
if (error) {
console.log('Transaction failed abnormally!', error);
qTrans.reject(error);
} else if (!committed) {
console.log('We aborted the transaction.');
qTrans.reject(error);
} else {
console.log('Success!');
console.log("Credit data: ", snapshot.val());
qTrans.resolve(snapshot.val());
}
});
return qTrans.promise;

Related

Why is the Axios .then and .catch function incrementing my index?

I have a loop that is making up to five calls to an API endpoint to validate ids. My increment variable turns from 0 to 1 on the first iteration before finishing the loop.
I pinpointed that it happens whether the request is good or bad after console logging the variable right before the request and then inside each callback. As soon as the variable is called in the .then callback or the .catch callback, the index is incremented and I have no idea why. I have tested different variable names and still get the same result. Anyone have an idea on this?
I also used the .fetch() method with React and the same thing is happening in the .then function so I don't think this is specific to axios.
Here is my function:
isValidAIN(ains) {
var control = this;
var length = ains.length;
if (ains.length > 0) {
for (var i = 0; i < length; i++) {
if (ains[i].length !== 10) {
if (ains[i].length === 0) {
this.state.errors["ain[" + i + "]"] = "";
this.state.validAINS[i] = true;
} else {
this.state.errors["ain[" + i + "]"] = "This AIN Number Must Contain 10 Digits";
this.state.validAINS[i] = false;
}
this.setState(this.state);
} else {
// v this logs 0
console.log("i: ", i);
axios
.get("/myendpoint/?ain=" + ains[i])
.then((res) => {
// v this logs 1
console.log("in then: ", i);
console.log("res: ", res);
control.state.errors["ain[" + i + "]"] = "";
control.state.validAINS[i] = true;
control.setState(control.state);
})
.catch((err) => {
// v this logs 1
console.log("i in catch", i);
if (err.response.status == 404) {
control.state.errors["ain[" + i + "]"] = "AIN Is Invalid";
console.log(control.state.errors["ain[" + i + "]"]);
control.state.validAINS[i] = false;
control.setState(control.state);
return false;
}
});
// fetch("/myendpoint/?ain=" + ains[i])
// .then(res => res.json())
// .then(
// (result) => {
// console.log("success: ", result);
// if(result == null) {
// control.state.errors["ain[" + i + "]"] = "AIN Is Invalid";
// control.state.validAINS[i] = false;
// control.setState(control.state);
// return false;
// }
// else {
// control.state.errors["ain[" + i + "]"] = "";
// control.state.validAINS[i] = true;
// control.setState(control.state);
// }
// },
// (error) => {
// console.log("error: ", error);
// }
// )
// .catch(
// (err) => {
// console.log("Error from catch: ", err);
// }
// )
}
}
} else {
return false;
}
}
The axios request is asynchronous, and the scope of variables declared with var is the enclosing function. When the axios request is complete, this variable will be the max of the loop, since the loop is synchronous and complete.
Change the var to let for i to use block scoping instead.
for(let i = 0; i < length; i++) { ... }
Because axios calls are asynchronous.
You're performing a for loop that just loops through ains.length, this happens in a few milliseconds. You define i as a var that is incremented after every loop within the for loop.
So if you're in the then / catch, the variable i is already at its maximum (so 1 in your case).
i is a variable and not a constant. When using i you're referencing to that pointer in memory. If you want i to be unique in every loop you should define it as let instead of var. This will create a unique value of i for each invocation of the loop.

Why is this async function failing to return any data?

So, a quick overview, this function is part of a larger app that ingests JSON data and prepares it to be rendered by Handlebars, which is then used for generating a PDF. This particular function has been giving me grief, as from my understanding of how async/await works, the data should be returned by the return returnArray at the bottom of the function. This however does not happen, and instead the empty array is returned. Could anyone offer insight as to why this is? (N.B. I've checked the data is present in iarr when it gets pushed, it's as though the return statement gets fired before the for loop has started.)
async function getPackageItem(item) {
try {
let returnArray = []
if (fs.existsSync(__dirname + "/../json/" + item.sku + ".json")) {
var file = fs.readFileSync(__dirname + "/../json/" + item.sku + ".json")
} else {
var file = fs.readFileSync(__dirname + "/../json/box.json")
}
const tb = JSON.parse(file);
for (var a = 0; a < item.quantity; a++) {
let iarr = [];
if (tb) {
tb.forEach(function(entry) {
ShopifyAuth.get('/admin/products/' + entry.product_id + '.json', (err, productData) => {
if (!err) {
ShopifyAuth.get('/admin/products/' + entry.product_id + '/metafields.json', (err, metafieldData) => {
if (!err) {
var itemObject = {};
var metaCounter = 0;
metafieldData.metafields.forEach(function(metadata) {
switch(metadata.key) {
case "notes": {
itemObject.wm_notes = metadata.value;
metaCounter++
break;
}
case "title": {
itemObject.title = metadata.value;
metaCounter++
break;
}
case "vintage": {
itemObject.year = metadata.value;
metaCounter++;
break;
}
case "shelfid": {
itemObject.shelf_id = metadata.value;
metaCounter++;
break;
}
case "bottleprice": {
itemObject.bottle_price = metadata.value;
metaCounter++;
break;
}
default: {
metaCounter++;
break;
}
}
if(metaCounter === metafieldData.metafields.length) {
itemObject.vendor = productData.product.vendor;
if (itemObject.title == undefined) {
itemObject.title = productData.product.title
}
if (itemObject.wm_notes == undefined) {
itemObject.wm_notes = " "
}
if (itemObject.year == undefined) {
itemObject.year = "Unspecified"
}
if (itemObject.shelf_id == undefined) {
itemObject.shelf_id = "N/A"
}
if (productData.product.images[1] == undefined) {
if (productData.product.images[0]) {
itemObject.logo = productData.product.images[0].src;
} else {
itemObject.logo = '';
};
} else {
itemObject.logo = productData.product.images[1].src;
}
itemObject.quantity = item.quantity;
iarr.push(itemObject)
if(iarr.length == tb.length) {
returnArray.push(iarr);
}
}
});
} else {
throw Error('Error retrieving product metadata');
}
})
} else {
throw Error('Error retrieving product data');
}
})
})
} else {
throw Error('Error loading JSON for specified box');
}
}
return returnArray;
} catch (e) {
console.log(e)
}
}
Edit: That's what I get for writing code at 3am, not sure how I missed that. Thanks for your feedback.
You marked your function async but you're not using await anywhere inside of it so you're not getting any of the benefits of using async. It doesn't make your function magically synchronous, you still have to manage asynchronicity carefully.
If ShopifyAuth.get supports returning a promise then await on the result instead of passing callbacks and your code will work, otherwise construct a Promise, do the async stuff in the promise, and return the promise from the function.
async function getPackageItem(item) {
let result = new Promise((resolve, reject) => {
// all your ShopifyAuth stuff here
if (err) {
reject(err);
}
resolve(returnArray);
});
return result;
}

JavaScript Promise Method not returning any data

I am creating a react native application.
I have a back button that fires the function findItem. findItem the uses async method searchJson. searchJson searches recursive json to find parent object based on id. However it never returns any results.
findItem:
findItem() {
//Pass null so top level json will be pulled
let result = this.searchJson(null).done();
let abv = 2;
// this.setState(previousState => {
// return {
// data: result,
// parentID: result.parentid
// };
// });
}
searchJson:
async searchJson(object) {
return new Promise(resolve => {
//use object or pull from porp - all data
let theObject = object == null ? this.props.data : object;
var result = null;
if (theObject instanceof Array) {
for (var i = 0; i < theObject.length; i++) {
result = this.searchJson(theObject[i]);
if (result) {
break;
}
}
}
else {
for (var prop in theObject) {
console.log(prop + ': ' + theObject[prop]);
if (prop == 'id') {
if (theObject[prop] == this.state.parentID) {
return theObject;
}
}
if (theObject[prop] instanceof Object || theObject[prop] instanceof Array) {
result = this.searchJson(theObject[prop]);
if (result) {
break;
}
}
}
}
if(result != null)
resolve(result);
});
}
Any help will be greatly appreciated.
Ok so I never got this to work but my workaround was this.
I Modified the findItem method:
findItem() {
let FinNode = null;
for (var node in this.props.data) {
FinNode = this.searchJson(this.state.parentID, this.props.data, this.props.data[node].book);
if (FinNode != null) {
this.setState(previousState => {
return {
data: FinNode[0].book.parentid == "" ? null : FinNode,
parentID: FinNode[0].book.parentid
};
});
break;
}
}
}
And then the searchJson:
searchJson(id, parentArray, currentNode) {
if (id == currentNode.id) {
return parentArray;
} else {
var result;
for (var index in currentNode.books) {
var node = currentNode.books[index].book;
if (node.id == id)
return currentNode.books;
this.searchJson(id, currentNode.books, node);
}
return null;
}
}
This allowed for all my nodes to be searched and the for loop made so that there is no need for async. This does have some drawbacks but seems to work decently without any massive performance issues.

Need help comparing the value inside array, with forEach loop

So basically I have some promise, forEach, just a lot of issues with this single problem I need to solve. So the variables I work with has below structure:
persons = [object, object, object]
where each object has { user:number , username: string, latitude:number, longitude:number}
from there I try to figure out if my user/username is inside of one of these objects, if not found id like it to be created, if found found id like it to update their location. Sounds simple, I think the problem has blown out of proportion but nothing works. The code I have now does not work, its either I can never figure out when the user is not there, or I can not figure out how to get to stop creating me every time it find a user who is not me.
var getswamp = function(item, index) {
return new Promise(function(resolve, reject){
var result = false;
if (item.user === user && item.username === username) {
if ((item.latitude !== latitudenew) || (item.longitude !== longitudenew)) {
var id = item.id;
swampdragon.update('locationcurrent', {
user: user,
latitude: latitudenew,
longititude: longitudenew,
username: username,
id: id
}, function (context, data) {
console.log("data updated", data);
result = true;
resolve(result);
}, function (context, data) {
console.log("You may not be updated");
});
} else {
console.log("No location change");
result = true;
}
}else{
if ( item.index === person.index){
console.log(person);
resolve(result)
}
}
});
};
person.forEach(function (item, index) {
var swamping = getswamp(item, index);
swamping.then(function (result) {
console.log(result);
if (result === true) {
console.log("We have you");
} else if (result === false && (index === person.length - 1)) {
console.log('Index: ' + index + ' Length of list is ' + person.length);
swampdragon.create('locationcurrent', {
user: user,
latitude: latitudenew,
longititude: longitudenew,
username: username
}, function (context, data) {
console.log("data created", data);
}, function (context, data) {
console.log("You may not be created")
});
}
})
});
Any help/ideas would just be great.
The Promise is used when some asynchronous event happened.
Since I can not create such event, I made a static code as below:
var persons = new Array();
persons.indexOf = function(person) {
var index = -1;
this.forEach(function(obj, i) {
if (person.username == obj.username) {
index = i;
}
});
return index;
}
persons.addOrUpdate = function(person) {
var index = this.indexOf(person);
if (index == -1) {
person.user = this.length + 1;
this.push(person);
}
else { // update case
var obj = this[index];
obj.latitude = person.latitude;
obj.longitude = person.longitude;
}
}
persons.print = function() {
var str = "";
this.forEach(function(obj) {
str += obj.user + ". " + obj.username + " at location (" +
obj.latitude + ":" + obj.longitude + ")\n";
});
return str;
}
persons.addOrUpdate({username:'Adam', latitude:-0.0045, longitude:14.2015});
persons.addOrUpdate({username:'Eve', latitude:-0.0045, longitude:14.2015});
persons.addOrUpdate({username:'Abel', latitude:-0.0045, longitude:14.2015});
// Updating Able location
var person = {username:'Abel', latitude:10.1145, longitude:14.1234};
persons.addOrUpdate(person);
alert(persons.print());

How to make loop wait for callback in node.js?

I have scenario. Where i Want to execute loop after data has been updated in mongodb. Means Like that :
var i = 0;
while (i< 5) {
attendanceDataModel.update(query, condition).exec(function(error, data) {
if (error) {
console.log("Error # 168 line in app.js File : \n" + err + "\n");
i++;
} else {
if (data.length <= 0) {
console.log("No Records Matched.");
i++;
} else {
console.log(currEmpId + " : successfully Logged Out ! :-)" + data + "\n");
updateRecordNumber(currRecordNumber);
i++; //wrong because it increases the value before updating in DB.
}
}
});
}
var updateRecordNumber = function(currRecordNumber) {
var condition = { deviceLogId: parseInt(currRecordNumber) };
lastDeviceLogIdModel.update({}, condition).exec(function(error, data) {
if (error) {
console.log("Error # 213 line in app.js File : \n" + err + "\n");
} else {
if (data.length <= 0) {
console.log("No Records Matched." + "\n");
} else {
console.log(currRecordNumber + " : DeviceLogId successfully Updated ! :-)");
// I want to increase value of i here after updation in database
}
}
});
}
Now, I want to increase variable i value after function updateRecordNumber has successfully updated
Simplest way is change function like var updateRecordNumber = function(currRecordNumber, callback) and then change invocation: updateRecordNumber(currRecordNumber, function(){ i++ });
But I think it's a much better solution to use some control flow approach, e.g. Promises or Async.js
P.S. of course you have to change function's body:
var updateRecordNumber = function(currRecordNumber, callback) {
// all your async stuff
callback();
}
Code can be changed to:
var i = 0;
function startUpdation() {
return attendanceDataModel.update(query, condition).exec(function(error, data) {
if (error) {
console.log("Error # 168 line in app.js File : \n" + err + "\n");
i++;
if (i<5) {
return startUpdation();
}
return;
} else {
if (data.length <= 0) {
console.log("No Records Matched.");
i++;
if (i<5) {
return startUpdation();
}
return;
} else {
console.log(currEmpId + " : successfully Logged Out ! :-)" + data + "\n");
return updateRecordNumber(currRecordNumber).then(function (err, data){
i++;
if (i<5) {
return startUpdation();
}
return;
});
}
}
});
}
function updateRecordNumber (currRecordNumber) {
var condition = { deviceLogId: parseInt(currRecordNumber) };
return lastDeviceLogIdModel.update({}, condition).exec(function(error, data) {
if (error) {
console.log("Error # 213 line in app.js File : \n" + err + "\n");
} else {
if (data.length <= 0) {
console.log("No Records Matched." + "\n");
} else {
console.log(currRecordNumber + " : DeviceLogId successfully Updated ! :-)");
}
}
});
}
startUpdation();
Please try this solution.
It would be better if you promisify function updateRecordNumber and write the increment call in the then().
Your while loop is synchronous and does not factor in that the response to your database operation will return sometimes later.
You need to requery after an unsuscessful operation through recursively rescheduling the operation (with an drop out after 5 tries):
function execute(count, callback) {
if (count == 5) {
return callback(new Error('meh...'))
}
loadSomethingAsync(function(error, result) {
if (error || !result) {
return execute(count++, callback)
}
callback(null, result)
})
}
execute(0, function(error, result) {
if (error) {
return console.log('after 5 tries still no result or error...')
}
console.log('yay, async op has finished!')
})
How about refactoring the loop to itself be part of the callback? Something like this:
var i = 0,
fna = function (error, data) {
if (error) {
console.log("Error # 168 line in app.js File : \n" + err + "\n");
fnc(); //i++;
} else {
if (data.length <= 0) {
console.log("No Records Matched.");
fnc(); //i++;
} else {
console.log(currEmpId + " : successfully Logged Out ! :-)" + data + "\n");
updateRecordNumber(currRecordNumber);
//i++; //wrong because it increases the value before updating in DB.
}
}
},
updateRecordNumber = function (currRecordNumber) {
var condition = {
deviceLogId : parseInt(currRecordNumber, 10)
};
lastDeviceLogIdModel.update({}, condition).exec(fnb);
},
fnb = function (error, data) {
if (error) {
console.log("Error # 213 line in app.js File : \n" + err + "\n");
} else {
if (data.length <= 0) {
console.log("No Records Matched." + "\n");
} else {
console.log(currRecordNumber + " : DeviceLogId successfully Updated ! :-)");
// I want to increase value of i here after updation in database
fnc();
}
}
},
fnc = function () {
i++;
if (i < 5) {
attendanceDataModel.update(query, condition).exec(fna);
}
};
attendanceDataModel.update(query, condition).exec(fna);
You can use synchronize module in node js .You can see my blog enter link description here

Categories

Resources