I recently changed my database from mongodb to rethinkdb. Since I'm getting some bluebird errors. So I guess I have to return my db queries differently.
Warning: a promise was created in a handler but was not returned from it
Code
It is hard to tell where the errors occur, thus I'm posting some examples of how my db queries are coded.
initialisation
exports.initDBandTables = (callback)->
db = self.getConfig().db
throw err "No DB Defined" if !db?
r.dbList().contains(db)
.do((databaseExists) ->
return r.branch(
databaseExists,
{ created: 0 },
r.dbCreate(db)
)
).run ()->
async.parallel
session: (next)->
self.ensureTable('sessions', null, next)
serverlogs: (next)->
self.ensureTable('serverlogs', null, next)
users: (next)->
self.ensureTable('users', null, next)
(err)->
return callback err if err?
return callback()
example get:
r.table('users').filter(newFilter).select(selector).limit(limit).sort({lastName:-1}).run (err, usersFound) ->
return callback err if err?
return callback null, usersFound
example create
r.table('users').insert(user).run (err, updatedUser)->
return callback err if err?
updatedUser = updatedUser || null
return callback null, updatedUser
Questions:
1) Is there a way to increase the verbosity? It is hard to investigate, without line numbers/ file/ or on which operation the error occurs.
2) The 1st error is related to promises which return undefined, but I'm returning my callbacks everywhere.
The issue was not with my code but with a module I used.
https://github.com/armenfilipetyan/express-session-rethinkdb/issues/8
Related
I've made firebase cloud function which adds the claim to a user that he or she has paid (set paid to true for user):
const admin = require("firebase-admin");
exports.addPaidClaim = functions.https.onCall(async (data, context) => {
// add custom claim (paid)
return admin.auth().setCustomUserClaims(data.uid, {
paid: true,
}).then(() => {
return {
message: `Succes! ${data.email} has paid for the course`,
};
}).catch((err) => {
return err;
});
});
However, when I'm running this function: I'm receiving the following error: "Unhandled Rejection (RangeError): Maximum call stack size exceeded". I really don't understand why this is happening. Does somebody see what could cause what's getting recalled which in turn causes the function to never end?
Asynchronous operations need to return a promise as stated in the documentation. Therefore, Cloud Functions is trying to serialize the data contained by promise returned by transaction, then send it in JSON format to the client. I believe your setCustomClaims does not send any object to consider it as an answer to the promise to finish the process so it keeps in a waiting loop that throws the Range Error.
To avoid this error I can think of two different options:
Add a paid parameter to be able to send a JSON response (and remove the setCustomUserClaim if it there isn’t any need to change the user access control because they are not designed to store additional data) .
Insert a promise that resolves and sends any needed information to the client. Something like:
return new Promise(function(resolve, reject) {
request({
url: URL,
method: "POST",
json: true,
body: queryJSON //A json variable I've built previously
}, function (error, response, body) {
if (error) {
reject(error);
}
else {
resolve(body)
}
});
});
Already spent quite a few hours trying to figure out the following, hopefully someone could point me.
In short what's happening: I have a firebase function that basically updates a database value in a transaction. But if I use transaction, the function always fail with this error:
Unhandled error RangeError: Maximum call stack size exceeded
at Function.isNull
Even though the transaction correctly updates the value in the database.
I was trying to debug it and remove anything I can. So whenever I remove transaction and use update() for example, the function finishes as expected with code 200.
As soon as I put transaction back the function fails with the following stacktrace:
Unhandled error RangeError: Maximum call stack size exceeded
at Function.isNull (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:11950:20)
at encode (/user_code/node_modules/firebase-functions/lib/providers/https.js:217:11)
at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13402:38
at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:4911:15
at baseForOwn (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:2996:24)
at Function.mapValues (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13401:7)
at encode (/user_code/node_modules/firebase-functions/lib/providers/https.js:242:18)
at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13402:38
at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:4911:15
at baseForOwn (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:2996:24)
at Function.mapValues (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13401:7)
Another similar stacktrace I've got:
Unhandled error RangeError: Maximum call stack size exceeded
at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13402:38
at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:4911:15
at baseForOwn (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:2996:24)
at Function.mapValues (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13401:7)
at encode (/user_code/node_modules/firebase-functions/lib/providers/https.js:242:18)
at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13402:38
at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:4911:15
at baseForOwn (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:2996:24)
at Function.mapValues (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13401:7)
at encode (/user_code/node_modules/firebase-functions/lib/providers/https.js:242:18)
Here is the function:
index.js:
const statsModule = require("./stats")
exports.myMethod9 = functions.https.onCall((data, context) => {
console.log("updateStatsMessagesChangedFeedbackHttp: data = ", data)
return statsModule.updateStatsMessagesChangedFeedbackHttp(data, context, admin);
});
stats.js
exports.updateStatsMessagesChangedFeedbackHttp = function (data, context, admin) {
var userId = "sdfsdf";
var feedback = data.feedback;
console.log("updateStatsMessagesChangedFeedbackHttp: userId = ", userId)
return exports.updateStatsMessagesChangedFeedback(userId, feedback, admin);
}
exports.updateStatsMessagesChangedFeedback = function (userId, feedback, admin) {
console.log("updateStatsMessagesChangedFeedback: feedback = ", feedback, ", userId = ", userId);
var root = admin.database().ref().root;
var path = constModule.statsUpdatedMessagesFeedbackPath + "/" + feedback;
var statsRef = root.child(path);
return statsRef.transaction(function (stats) {
if(!stats) {
stats = {votedUsers:{}, count: 0};
}
stats.votedUsers[userId] = true;
console.log("updateStatsMessagesChangedFeedback: stats = ", stats)
return stats;
}, function (error, committed, snapshot) {
//nothing wrong here - error is always null, committed - true
var snapVal = snapshot ? snapshot.val() : null;
console.log("error = ", error, ", committed = ", committed, ", data = ", snapVal);
if (error) {
// The fetch succeeded, but the update failed.
console.error("updateStatsMessagesChangedFeedback: The fetch succeeded, but the update failed: ", error);
} else {
console.log("updateStatsMessagesChangedFeedback: all ok: data = ", snapVal);
}
})
.catch(function (error) {
//this is never called - all good here as well
console.error("updateStatsMessagesChangedFeedback: error = ", error)
});
}
I call it from client web sdk like this:
var call = firebase.functions().httpsCallable('myMethod9');dFeedbackHttp
call({feedback: "some data"}).then(function (result) {...})
Neither catch, nor the transaction callback shows any errors. Though the function still fails only if I use transaction.
(click the image to enlarge)
Any ides on what's going on?
After discussion with support (very helpful guys by the way!) here is the solution:
one need to return a value from function in case of transaction:
exports.myMethod9 = functions.https.onCall((data, context) => {
console.log("updateStatsMessagesChangedFeedbackHttp: data = ", data)
return statsModule.updateStatsMessagesChangedFeedbackHttp(data, context, admin);
}).then(function(){
return {}
});
I don't have any clue on why returning a value makes any difference here as for example update() returns void which still makes the function complete with code 200.
Waiting on further comments form the support and meanwhile if anyone has any info on this feel free to share.
I'm creating app with using SQLite. I have some problems with using SQLite.
I have page with variable for managing database.
public sqlite_object: any;
In the constructor i'm opening/creating if not exists database, and saving db object to variable.
constructor(...){
let name = 'db_name';
this.openDatabase(name);
}
openDatabase(name){
let db = new SQLite();
db.create({ name: name, location: 'default' })
.then( (db_obj: SQLiteObject) => {
this.sqlite_object = db_obj
}, (error) => {
alert('error');
}
}
So, in the constructor i'm opening db, and saving it for future.
On of buttons calling that function:
testSQL(sql_queries){
this.sqlite_object.transaction( function(tx){
Object.keys(sql_queries)
.sort()
.forEach( function(v,i){
tx.executeSql(sql_queries[v],
null,
function (transaction, result){
alert('executing sql');
},
function (transaction, error){
alert('error');
});
});
}, function (error){
alert('error2');
}, function (){
alert('success');
}
}
My sql_queries have about ~30 queries (correct and incorrect).
When i put alert into forEach(), it will be executed everytime (same times as sql_queries length).
When executeSql(...) getting incorrect query, alert about error is showing, but i never seen alert 'executing sql' - is sth wrong here? (i don't know if my queries executing correctly)
Also i have one more question. How can i get list of tables from my database?
your "executing sql.." is inside a success callback function. it will be executed after the entire for each() query is successful (not after each loop). So as you said it's showing error. So, It will not execute the successcallback function. else it will execute the errorcallback function. this is beacause your entire query is not successful. I hope you get the point
Suppose I have 2 collections "PlanSubscriptions" and "ClientActivations". I am serially doing a insert on both the collections.
Later one depends on previous one, if any of the transaction fails then the entire operation must rollback.
How can I achieve that in Meteor 1.4?
Since MongoDB doesn't support atomicity, you will have to manage it with Method Chaining.
You can write a method, say, transaction where you will call PlanSubscriptions.insert(data, callback). Then in the callback function you will call ClientActivations.insert(data, callback1) if the first insertion is success and in callback1 return truthy if second insertion is succes, otherwise falsy. If the first insertion returns error you don't need to do anything, but if the second insertion returns error then remove the id got from the insertion in first collection.
I can suggest following structure:
'transaction'(){
PlanSubscriptions.insert(data, (error, result)=>{
if(result){
// result contains the _id
let id_plan = result;
ClientActivations.insert(data, (error, result)=>{
if(result){
// result contains the _id
return true;
}
else if(error){
PlanSubscriptions.remove(id_plan);
return false;
}
})
}
else if(error){
return false;
}
})
}
There is no way to do that in Meteor, since mongodb is not an ACID-compliant database. It has a single-document update atomicity, but not a multiple-document one, which is your case with the two collections.
From the mongo documentation:
When a single write operation modifies multiple documents, the modification of each document is atomic, but the operation as a whole is not atomic and other operations may interleave.
A way to isolate the visibility of your multi-document updates is available, but it's probably not what you need.
Using the $isolated operator, a write operation that affects multiple documents can prevent other processes from interleaving once the write operation modifies the first document. This ensures that no client sees the changes until the write operation completes or errors out.
An isolated write operation does not provide “all-or-nothing” atomicity. That is, an error during the write operation does not roll back all its changes that preceded the error.
However, there are a couple of libraries which try to tackle the problem at the app-level. I recommend taking a look at fawn
In your case, where you have exactly two dependent collections, it's possible to take advantage of the two phase commits technique. Read more about it here: two-phase-commits
Well I figured it out myself.
I added a package babrahams:transactions
At server side Meteor Method call, I called tx Object that is globally exposed by the package. The overall Server Side Meteor.method({}) looks like below.
import { Meteor } from 'meteor/meteor';
import {PlanSubscriptions} from '/imports/api/plansubscriptions/plansubscriptions.js';
import {ClientActivations} from '/imports/api/clientactivation/clientactivations.js';
Meteor.methods({
'createClientSubscription' (subscriptionData, clientActivationData) {
var txid;
try {
txid = tx.start("Adding Subscription to our database");
PlanSubscriptions.insert(subscriptionData, {tx: true})
ClientActivations.insert(activation, {tx: true});
tx.commit();
return true;
} catch(e){
tx.undo(txid);
}
return false;
}
});
With every insert I had added {tx : true}, this concluded it to be a apart of transaction.
Server Console Output:
I20170523-18:43:23.544(5.5)? Started "Adding Subscription to our database" with
transaction_id: vdJQvFgtyZuWcinyF
I20170523-18:43:23.547(5.5)? Pushed insert command to stack: vdJQvFgtyZuWcinyF
I20170523-18:43:23.549(5.5)? Pushed insert command to stack: vdJQvFgtyZuWcinyF
I20170523-18:43:23.551(5.5)? Beginning commit with transaction_id: vdJQvFgtyZuWcinyF
I20170523-18:43:23.655(5.5)? Executed insert
I20170523-18:43:23.666(5.5)? Executed insert
I20170523-18:43:23.698(5.5)? Commit reset transaction manager to clean state
For more Information you can goto link : https://github.com/JackAdams/meteor-transactions
NOTE: I am using Meteor 1.4.4.2
Just sharing this link for future readers:
https://forums.meteor.com/t/solved-transactions-with-mongodb-meteor-methods/48677
import { MongoInternals } from 'meteor/mongo';
// utility async function to wrap async raw mongo operations with a transaction
const runTransactionAsync = async asyncRawMongoOperations => {
// setup a transaction
const { client } = MongoInternals.defaultRemoteCollectionDriver().mongo;
const session = await client.startSession();
await session.startTransaction();
try {
// running the async operations
let result = await asyncRawMongoOperations(session);
await session.commitTransaction();
// transaction committed - return value to the client
return result;
} catch (err) {
await session.abortTransaction();
console.error(err.message);
// transaction aborted - report error to the client
throw new Meteor.Error('Database Transaction Failed', err.message);
} finally {
session.endSession();
}
};
import { runTransactionAsync } from '/imports/utils'; // or where you defined it
Meteor.methods({
async doSomething(arg) {
// remember to check method input first
// define the operations we want to run in transaction
const asyncRawMongoOperations = async session => {
// it's critical to receive the session parameter here
// and pass it to every raw operation as shown below
const item = await collection1.rawCollection().findOne(arg, { session: session });
const response = await collection2.rawCollection().insertOne(item, { session: session });
// if Mongo or you throw an error here runTransactionAsync(..) will catch it
// and wrap it with a Meteor.Error(..) so it will arrive to the client safely
return 'whatever you want'; // will be the result in the client
};
let result = await runTransactionAsync(asyncRawMongoOperations);
return result;
}
});
I am newer for IronRouter, why readFile callback executed the response are send to client.
Router.map(
()->
this.route 'readFile',
path: '/readFile'
where: 'server'
method: 'GET'
action: ()->
self = this
fs.readFile '/tmp/a.txt', (err, data)->
if err
throw err
console.log(data.toString())
self.response.writeHead(200, {'Content-Type': 'text/plain'})
self.response.end(data)
console.log('response ...')
)
http.js:733
W2049-12:04:26.781(8)? (STDERR) throw new Error('Can\'t render headers after they are sent to the client.'
W2049-12:04:26.781(8)? (STDERR) ^
W2049-12:04:26.782(8)? (STDERR) Error: Can't render headers after they are sent to the client.
But, I use express , like this is work well.
exports.index = function(req, res){
fs.readFile('/tmp/a.txt', function (err, data) {
if (err) throw err;
console.log(data.toString());
res.send(200, data.toString());
});
console.log('response ...');
};
thanks #ChristianF #Tarang Use Meteor._wrapAsync or Future all work well . when I use self define function replace fs.readFile. It take throw ex . I Doubt My defined function has error. As follows:
#getAccounts = (callback) ->
query = "SELECT Id, Name, AccountNumber FROM Account"
queryBySoql(query, (err, result)->
if err
return console.error(err)
return callback(result)
)
I invoked link this:
# using Meteor
#data = Meteor._wrapAsync(getAccounts)()
#using Future
waiter = Future.wrap(getAccounts)()
data = waiter.wait()
this.response.writeHead 200, {'Content-Type': 'text/plain'}
this.response.end JSON.stringify(data)
thanks all.
Just today I struggled with this very issue. The answer, it seems to me, is that meteor (or at least the iron router) doesn't handle async calls the way you'd expect. The solution is to wrap the async call into a fiber, which is the mechanism meteor uses to keep the programming synchronous.
In your case try this (sorry, I don't know coffeescript very well):
var Future = Npm.require('fibers/future');
...
var waiter = Future.wrap(fs.readFile);
var data = waiter('/tmp/a.txt').wait();
response.writeHead(200, {'Content-Type': 'text/plain'})
response.end(data)
EDIT Addressing the addition to the question.
Functions wrapped in a future need to have a callback as their last argument that has the (err, result) signature. Try this:
#getAccounts = (callback) ->
query = "SELECT Id, Name, AccountNumber FROM Account"
queryBySoql(query, (err, result)->
if err
return console.error(err)
return callback(err, result)
)
You could wrap up your read file request's callback into the same fiber. It will not block other requests & comes out quite clean.
readFile = Meteor_wrapAsync(fs.readFile.bind(fs))
data = readFile("/tmp/a.txt")
console.log data
#response.writeHead(200, {'Content-Type': 'text/plain'})
#response.end data
return