this.removed(collection, id) makes somewhere exception thrown - collections

I'm trying to inform the subscribers when a document is removed from a collection. I use this.removed(collection, id) when removed function of observeChanges is called:
Meteor.publish('tasks_listsPub', function(sUrl){
...
var self = this;
ocTasksLists.find().observeChanges({
added: function (sId, oFields) {
console.log('added:'+sId);
self.added('tasks_lists', sId, oFields);
},
removed: function (sId) {
console.log('removed:'+sId);
self.removed('tasks_lists', sId); //throws a exception but sometimes it works in the browser
},
changed: function(sId, oFields){
console.log('changed:'+sId);
self.changed('tasks_lists', sId, oFields);
}
});
var cVisibleTasksLists = ocTasksLists.find({_id: {$in: oWs.tasks_lists}});
return cVisibleTasksLists;
});
The problem is that server throws exception:
removed:K8BBys7WRH4tTQRBg
Exception in queued task: Error: Removed nonexistent document K8BBys7WRH4tTQRBg
at _.extend.removed (app/packages/livedata/livedata_server.js:181:17)
and the other browsers sometimes does not remove the deleted document. Any solution? Thx

You appear to be publishing two conflicting datasets within one publish function.
The self.added, self.removed, and self.changed functions inside the observeChanges is trying to keep the client updated with everything in ocTasksLists.
return cVisibleTasksLists; however is trying to only publish the subset of ocTasksLists that match your query.
These conflicting publish instructions lead to the client sometimes not having all documents that are removed from ocTasksLists - your error message results.
Whether you want the whole dataset or the subset either can be done just by returning the database cursor as you do in your last two lines. Removing the observeChanges function along with .added, .removed, and .changed functions will fix your error.

Related

How do I throw an error asynchronously in RxJs?

I have an observer that tracks questions and answers of a command line interface. What I would like to do is inject an error into the observer given a certain event in my code in order to terminate the observer and its subscription downstream. It is unknown at what time it runs.
I've tried throwing errors from a merge of a subject and the observable but I cannot seem to get anything out of it.
Here is the relevant code:
this.errorInjector$ = new Subject<[discord.Message, MessageWrapper.Response]>();
....
this.nextQa$ = merge(
nextQa$,
this.errorInjector$.pipe(
tap((): void => {
throw (new Error('Stop Conversation called'));
}),
),
);
// start conversation
Utils.logger.trace(`Starting a new conversation id '${this.uuid}' with '${this.opMessage.author.username}'`);
}
getNextQa$(): Observable<[discord.Message, MessageWrapper.Response]> {
return this.nextQa$;
}
stopConversation(): void {
this.errorInjector$.next(
null as any
);
}
The this.nextQa$ is merged with the local nextQa$ and the errorInjector$. I can confirm that stop conversation is being called and downstream is receiving this.nextQa$ but I am not seeing any error propagate downstream when I try to inject the error. I have also tried the this.errorInjector.error() method and the map() operator instead of tap(). For whatever reason I cannot get the two streams to merge and to throw my error. To note: this.nextQa$ does propagate errors downstream.
I feel like I am missing something about how merge or subjects work so any help or explanation would be appreciated.
EDIT
Well I just figured out I need a BehaviorSubject instead of a regular subject. I guess my question now is why do I need a BehaviorSubject instead of a regular Subject just to throw an error?
EDIT 2
BehaviorSubject ALWAYS throws this error which is not what I want. It's due to the nature of its initial emission but I still don't understand why I can't do anything with a regular subject in this code.
First of all if you want subject to work you will have to subscribe before the error anyting is emitted. So there is a subscription sequence problem within your code. If you subscribe immediately after this.nextQa$ is created you shouldn't miss the error.
this.nextQa$ = merge(
nextQa$,
this.errorInjector$.pipe(
tap((): void => {
throw (new Error('Stop Conversation called'));
}),
),
);
this.nextQa$.subscribe(console.log,console.error)
The problem is getting the object with the stopConversation(): void from the dictionary object I have. The this object is defined and shows errorInjector$ is defined but the debugger tells me that errorInjector$ has become undefined when I hover over the value. At least that's the problem and I'll probably need to ask another question on that.

UnhandledPromiseRejectionWarning when using Fluture `encaseP` on `fetch`

I have just started using Flutures, and I am trying to fetch some remote data for a visualization with d3.
I created a function which accepts a DOM selector (e.g. #my-chart) and a url (e.g. https://example.com/data.json).
If an error occurs when fetching the data, I have a unary function that shows an error message. If everything goes well, I have a unary function that draws the visualization. For the sake of simplicity, let's suppose that these functions are just console.error and console.log.
const fn = async (selector, url) => {
// convert fetch (which returns a Promise) into a function that
returns a Future
const fetchf = Future.encaseP(fetch);
fetchf(url)
.chain(res => Future.tryP(_ => res.json()))
.fork(console.error, console.log);
}
Apparently I am missing something when wrapping fetch in a Future, because I get this warning:
UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch().
If I had to use async/await I would write something like this, which would not give me any warning.
const fn = async (selector, url) => {
let res;
try {
res = await fetch(url);
} catch (err) {
console.error(err);
return;
}
let data;
try {
data = res.json();
} catch (err) {
console.error(err);
return;
}
console.log(data);
};
It seems two things are going on here:
The data.json() function is not supposed to be wrapped inside tryP, because according to your second not broken example, it returns synchronously (there is no await). This would cause Fluture to raise a TypeError (because it expects to see a Promise, but gets a JSON value). Although, knowing the fetch API, data.json() typically does return a Promise, so it might also be your second example is broken, and something else is going on. Whatever it is, I suspect that an unexpected Error is being thrown somewhere. Do you see any other error messages in your console, besides the one you posted?
I did some testing, and it does seems to be true - when Fluture raises or catches a TypeError after a successful encaseP, it seems the original Promise manages to catch that error, and trigger the unhandled rejection. This appears to be a regression bug in Fluture, and I will be fixing it soon. In the meantime, if we get to the bottom of what's throwing your error, you will be able to continue without depending on said fix.
EDIT: I've opened a PR to fix the second issue: https://github.com/fluture-js/Fluture/pull/310
EDIT2: The fix has been released under version 10.3.1. Using that version should give you more insights in what's happening with issue 1.

Retrieve values from firebase database in conversation flow

I am trying to grab information from my firebase database after a particular intent is invoked in my conversation flow.
I am trying to make a function which takes a parameter of user ID, which will then get the highscore for that user, and then say that users highscore back to them.
app.intent('get-highscore', (conv) => {
var thisUsersHighestscore = fetchHighscoreByUserId(conv.user.id);
conv.ask('your highest score is ${thisUsersHighestScore}, say continue to keep playing.');
});
function fetchHighscoreByUserId(userId){
var highscoresRef = database.ref("highscores");
var thisUsersHighscore;
highscoresRef.on('value',function(snap){
var allHighscores= snap.val();
thisUsersHighscore = allHighscores.users.userId.highscore;
});
return thisUsersHighscore;
}
An example of the data in the database:
"highscores" : {
"users" : {
"1539261356999999924819020" : {
"highscore" : 2,
"nickname" : "default"
},
"15393362381293223232222738" : {
"highscore" : 78,
"nickname" : "quiz master"
},
"15393365724084067696560" : {
"highscore" : "32",
"nickname" : "cutie pie"
},
"45343453535534534353" : {
"highscore" : 1,
"nickname" : "friendly man"
}
}
}
It seems like it is never setting any value to thisUsersHighScore in my function.
You have a number of issues going on here - both with how you're using Firebase, how you're using Actions on Google, and how you're using Javascript. Some of these issues are just that you could be doing things better and more efficiently, while others are causing actual problems.
Accessing values in a structure in JavaScript
The first problem is that allHighscores.users.userId.highscore means "In an object named 'allHighscores', get the property named 'users', from the result of that, get the property named 'userId'". But there is no property named "userId" - there are just a bunch of properties named after a number.
You probably wanted something more like allHighscores.users[userId].highscore, which means "In an object named 'allHighscores', get the property named 'users', fromt he result of that, get the property named by the value of 'userId'".
But if this has thousands or hundreds of thousands of records, this will take up a lot of memory. And will take a lot of time to fetch from Firebase. Wouldn't it be better if you just fetched that one record directly from Firebase?
Two Firebase Issues
From above, you should probably just be fetching one record from Firebase, rather than the whole table and then searching for the one record you want. In firebase, this means you get a reference to the path of the data you want, and then request the value.
To specify the path you want, you might do something like
var userRef = database.ref("highscores/users").child(userId);
var userScoreRef = userRef.child( "highscore" );
(You can, of course, put these in one statement. I broke them up like this for clarity.)
Once you have the reference, however, you want to read the data that is at that reference. You have two issues here.
You're using the on() method, which fetches the value once, but then also sets up a callback to be called every time the score updates. You probably don't need the latter, so you can use the once() method to get the value once.
You have a callback function setup to get the value (which is good, since this is an async operation, and this is the traditional way to handle async operations in Javascript), but you're returning a value outside of that callback. So you're always returning an empty value.
These suggest that you need to make fetchHighScoreByUserId() an asynchronous function as well, and the way we have to do this now is to return a Promise. This Promise will then resolve to an actual value when the async function completes. Fortunately, the Firebase library can return a Promise, and we can get its value as part of the .then() clause in the response, so we can simplify things a lot. (I strongly suggest you read up on Promises in Javascript and how to use them.) It might look something like this:
return userScoreRef.once("value")
.then( function(scoreSnapshot){
var score = scoreSnapshot.val();
return score;
} );
Async functions and Actions on Google
In the Intent Handler, you have a similar problem as above. The call to fetchHighScoreByUserId() is async, so it doesn't finish running (or returning a value) by the time you call conv.ask() or return from the function. AoG needs to know to wait for an async call to finish. How can it do that? Promises again!
AoG Intent Handlers must return a Promise if there is an asyc call involved.
Since the modified fetchHighScoreByUserId() returns a Promise, we will leverage that. We'll also set our response in the .then() part of the Promise chain. It might look something like this:
app.intent('get-highscore', (conv) => {
return fetchHighscoreByUserId(conv.user.id)
.then( function(highScore){
conv.ask(`Your highest score is ${highScore}. Do you want to play again?`);
} );
});
Two asides here:
You need to use backticks "`" to define the string if you're trying to use ${highScore} like that.
The phrase "Say continue if you want to play again." is a very poor Voice User Interface. Better is directly asking if they want to play again.

AddToSet operation requires a target array field

Trying to make use of Azure DocumentDB/CosmsoDB using the MongoDB driver. I have learned that there are many limitations as the full set of features is not currently implemented. I want to use aggregate functions, specifically $group, and .distinct but I don't think that is available yet. As a work around, I am trying to maintain a separate "tracking" document to enable "distinct". trying to update a document using $addToSet, but getting the following:
MongoError: Message: {"Errors":["Encountered exception while executing function. Exception = Error: AddToSet operation requires a target array field.\r\nStack trace: Error: AddToSet operation requires a target array field.\n at arrayAddToSet (__.sys.commonUpdate.js:2907:25)\n at handleUpdate (__.sys.commonUpdate.js:2649:29)\n at processOneResult (__.sys.commonUpdate.js:2484:25)\n at queryCallback (__.sys.commonUpdate.js:2461:21)\n at Anonymous function (__.sys.commonUpdate.js:619:29)"]}
The update command i am using:
var usersDocument = collection.updateOne(
{ "type": "users" },
{ $addToSet: {users: "someone#gmail.com"} },
function(err, count, status) {
console.log("updateOne err: " + err)
console.log("updateOne count: " + count)
console.log("updateOne status: " + status)
}
)
This seems to me to be a pretty straight-forward command, pulled from the mongo documentation and fields adjusted as needed. Maybe I am missing something really basic?
My ultimate goal was to make sure that my code was portable as to be able to move it into a Mongo cluster, if I so desired (not be locked into Azure-specific). To get started and not have to manage a multi-server cluster, Azure CosmosDB looked like a great jumpstart, but the limitations are maddening.
UPDATE:
Now that I have fixed my document and I actually have a field with an array, $addToSet is just replacing the value, rather than adding to the array. I'll create a new question for that.
Yup, something basic. The error message was actually correct. After inspecting the existing document:
I found:
{ "users": "[]" }
And changed it to:
{ "users": [] }
Now it is working.

Meteor insert into collection appears to work, but remains empty

I'm doing a simple insert into a meteor collection that appears work, but leaves the collection empty.
The collection is defined properly on the server:
Meteor.publish("comments", function () {
return Comments.find();
});
Subscribed to properly in the client.js:
Meteor.subscribe("commments");
And set up properly on the model.js:
Comments = new Meteor.Collection("comments");
The insert code is as follows:
Meteor.methods({
addComment: function (options) {
check(options.post_id, String);
check(options.comment, NonEmptyString);
if (! this.userId)
throw new Meteor.Error(403, "You must be logged in to comment.");
if (options.comment.length > 1000)
throw new Meteor.Error(413, "Comment is too long");
var post = Posts.findOne(options.post_id);
if (! post)
throw new Meteor.Error(404, "No such post");
// add new comment
var timestamp = (new Date()).getTime();
console.log('Comment: ' + options.comment);
console.log('Post: ' + options.post_id);
console.log('UserId: ' + this.userId);
var saved = Comments.insert({
owner: this.userId,
post_id: options.post_id,
timestamp: timestamp,
text: options.comment});
console.log('Saved: ' + saved);
}
});
Once the insert is called, the console prints out the following:
Comment: Something
Post: xRjqaBBEMa6qjGnDm
UserId: SCz9e6zrpcQrKXYWX
Saved: FCxww9GsrDsjFQAGF
> Comments.find().count()
0
I have inserts into several other collections that work just fine (Posts being one of them as you can see the post ID in the code). In the docs ist said that if the insert errors out it will print to the console, but as you can see it appears to be working, yet is actually empty.
Thanks.
UPDATE: I did find that the data is being put into the database, but for some reason is not showing up. I'm not sure why the data is not being published properly since there are no filters on the find().
I'm not sure exactly what's wrong, but there's a few things to check here.
• First, this:
Meteor.publish("comments", function () {
return Comments.find();
});
directs the server to publish the Collection, but doesn't actually establish the collection server side.
You should have Comments = new Meteor.Collection("comments"); available on both the client and the server. I tend to put in a file called model.js like the examples tend to do.
• Second possibility, you don't have a subscribe function shown above, such as Meteor.subscribe("comments"); If you don't have a subscribe function, your client isn't going to know about it, even though it does exist in the collection.
You can test this theory by typing meteor mongo in the shell (with your Meteor app running), and db.comments.find() to see if your comments are actually in the database but not subscribed to.
Verify you do not have an error in your client code. With Meteor.call, if you do not initialize a variable you can have an error condition that will block reactive updating in your templates but continue to write fine to your console just before hand.
I've made that mistake which I talk about here:
http://doctormehmet.blogspot.com/2013/07/revoltdc-hackathon-20130622-iteration-3.html
Specifically I had something like
Template.mytemplate.helpers({
somevar: function({
if (some_session_var_set_by_a_call.party){
//do something
}
}
Now the somevar function gets called on render, before the Meteor.call returns. Therefore the variable some_session_var_set_by_a_call isn't set yet. The whole thing stops client side on the undefined error.

Resources