Why are the functions called several times within the FlatMaps? - combine

I have the following code:
func myFunction() -> AnyPublisher<MyObject, MyError> {
return self.globalPublisher
// FlatMap 1
.flatMap { value1 -> AnyPublisher<Object1, Error1 > in
return self.function1(value1)
}
// FlatMap 2
.flatMap { value2 -> AnyPublisher<Object2, Error2 > in
return self.function1(value2)
}
// FlatMap3
.flatMap { value3 -> AnyPublisher<Object3, Error3 > in
return self.function1(value3)
}
.eraseToAnyPublisher()
})
}
myFunction has only one subscriber (checked with debug). globalPublisher can be triggered multiple times and at any time. This triggers the whole flatMap logic.
When the globalPublisher is triggered for the first time, everything is fine: every function in every flatMap block is called once. But the second time happens something strange. The globalPublisher is triggered only once. The function in FlatMap 1 is also triggered only once and returns only one value (checked with debug). But the function in FlatMap 2 is suddenly triggered twice and returns two values. The function in FlatMap 3 is then triggered 6 times.
The same thing happens for the third and further times: globalPublisher and the function in the FlatMap 1 are triggered once and the function1() returns only one value. The rest is triggered several times and the number of triggers is getting bigger and bigger.
Could someone tell me what could be the reason for such strange behavior of FlatMaps? I have already gone through my code several times and debugged it. Actually, everything must work. I suppose it's possible that the global publisher is storing somehow the "subscriptions" of the FlatMaps? But I don't think it works that way. Do you have some ideas?
I suspect that the problem lies in the combination of a global publisher and all the FlatMaps.
Thanks in advance.

I see two options here.
Either
So flatmap has a maxPublishers parameter check here. The parameter is set default to .unlimited which is controlling how many publishers the method can accept.
That's why your flatmaps are publishing these values unlimited. You need to change the parameter and set it to something like flatMap(maxPublishers: .max(1))
Or
You can use switchToLatest which will always use the most recent provided publisher.

Related

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.

What is the canonical name for a function that ensures that an asynchronous task takes at least x time to resolve?

Say we have an asynchronous function that may resolve within any duration. However fast it may be, we want to ensure it never resolves before t time as passed. To achieve this in a reusable way, we create a function (here in JavaScript):
async function stall(task, minDuration) {
// Wait until both `task` and `delay(minDuration)` resolves
// and return the results from `task`
const results = await Promise.all([task, delay(minDuration)])
return results[0]
}
where delay() is a simple function that resolves after a given amount of time.
Is there a canonical name for the stall() function?

Returned value from Meteor Helper not showing up in template

I have a Meteor Helper that does a GET request and am supposed to get response back and pass it back to the Template, but its now showing up the front end. When I log it to console, it shows the value corerctly, for the life of mine I can't get this to output to the actual template.
Here is my helper:
UI.registerHelper('getDistance', function(formatted_address) {
HTTP.call( 'GET', 'https://maps.googleapis.com/maps/api/distancematrix/json? units=imperial&origins=Washington,DC&destinations='+formatted_address+'&key=MYKEY', {}, function( error, response ) {
if ( error ) {
console.log( error );
} else {
var distanceMiles = response.data.rows[0].elements[0].distance.text;
console.log(response.data.rows[0].elements[0].distance.text);
return distanceMiles;
}
});
});
In my template I pass have the following:
{{getDistance formatted_address}}
Again, this works fine and shows exactly what I need in the console, but not in the template.
Any ideas what I'm doing wrong?
I posted an article on TMC recently that you may find useful for such a pattern. In that article the problem involves executing an expensive function for each item in a list. As others have pointed out, doing asynchronous calls in a helper is not good practice.
In your case, make a local collection called Distances. If you wish, you can use your document _id to align it with your collection.
const Distances = new Mongo.collection(); // only declare this on the client
Then setup a function that either lazily computes the distance or returns it immediately if it's already been computed:
function lazyDistance(formatted_address){
let doc = Distances.findOne({ formatted_address: formatted_address });
if ( doc ){
return doc.distanceMiles;
} else {
let url = 'https://maps.googleapis.com/maps/api/distancematrix/json';
url += '?units=imperial&origins=Washington,DC&key=MYKEY&destinations=';
url += formatted_address;
HTTP.call('GET',url,{},(error,response )=>{
if ( error ) {
console.log( error );
} else {
Distances.insert({
formatted_address: formatted_address,
distanceMiles: response.data.rows[0].elements[0].distance.text
});
}
});
}
});
Now you can have a helper that just returns a cached value from that local collection:
UI.registerHelper('getDistance',formatted_address=>{
return lazyDistance(formatted_address);
});
You could also do this based on an _id instead of an address string of course. There's a tacit assumption above that formatted_address is unique.
It's Meteor's reactivity that really makes this work. The first time the helper is called the distance will be null but as it gets computed asynchronously the helper will automagically update the value.
best practice is not to do an async call in a helper. think of the #each and the helper as a way for the view to simply show the results of a prior calculation, not to get started on doing the calculation. remember that a helper might be called multiple times for a single item.
instead, in the onCreated() of your template, start the work of getting the data you need and doing your calculations. store those results in a reactive var, or reactive array. then your helper should do nothing more than look up the previously calculated results. further, should that helper be called more times than you expect, you don't have to worry about all those additional async calls being made.
The result does not show up because HTTP.call is an async function.
Use a reactiveVar in your case.
Depending on how is the formated_address param updated you can trigger the getDistance with a tracker autorun.
Regs
Yann

Queuing asynchronous actions in reflux

When using RefluxJS stores with asynchronous actions, you can easily end up having race conditions between your actions.
Abstract description of the issue
For example, our store is in state X. An async action A is called from X, and before it finishes, another async action B is called, also from X. From here, no matter which action finishes first, it goes wrong.
B finishes first with a state Y1, A finishes last and overwrites the state Y1 with Y2.
A finishes first with a state Y2, B overwrites Y2 with Y1.
The desired behavior would be to have:
A B
X -> Y -> Z
Where B is not based on X, but on Y, and leads to a consistent Z state, instead of two actions based on the same state, leading to an inconsistent state:
A
X -> Y1 .--> Y2
\ /
'----'
B
Implemented example of the issue
I wrote a minimal working example, running with Node, of the problem I am talking about.
var Q = require('q');
var Reflux = require('reflux');
var RefluxPromise = require('reflux-promise');
Reflux.use(RefluxPromise(Q.Promise));
var AsyncActions = Reflux.createActions({
'add': { asyncResult: true }
});
var AsyncStore = Reflux.createStore({
init: function () {
// The state
this.counter = 0;
AsyncActions.add.listenAndPromise(this.onAdd, this);
},
// Increment counter after a delay
onAdd: function(n, delay) {
var that = this;
return apiAdd(this.counter, n, delay)
.then(function (newCounter) {
that.counter = newCounter;
that.trigger(that.counter);
});
}
});
// Simulate an API call, that makes the add computation. The delay
// parameter is used for testing.
// #return {Promise<Number>}
function apiAdd(counter, n, delay) {
var result = Q.defer();
setTimeout(function () {
result.resolve(counter + n);
}, delay);
return result.promise;
}
// Log the store triggers
AsyncStore.listen(console.log.bind(undefined, 'Triggered'));
// Add 3 after 1 seconds.
AsyncActions.add(3, 1000);
// Add 100 almost immediately
AsyncActions.add(100, 1);
// Console output:
// > Triggered 100
// > Triggered 3
// Desired output (queued actions):
// > Triggered 3
// > Triggered 103
With these dependencies in package.json
{
"dependencies": {
"q": "^1.3.0",
"reflux": "^0.3",
"reflux-promise": "^1"
}
}
Nature of the question
I expected RefluxJS to queue actions, but it doesn't. So I am looking for a way to order these actions correctly. But even if I managed to queue up these actions in some way (so B is issued after A) how can I be certain that, when A finishes, issuing B is still a valid action ?
Maybe I am using RefluxJS the wrong way in the first place, and this scenario does not happen in a properly structured app.
Is queuing of the asynchronous actions (assuming this is possible within a Reflux app) the solution ? Or should we work on avoiding these scenarios in the first place, somehow ?
Your example seems like more of an issue with the concept of "source of truth" than anything else. You're storing the current state of the number ONLY client side, but ONLY updating it after receiving confirmation from the server side on an operation being done to it.
Of course that'll make issues. You're mixing the actions upon the number and the storage of the number in a weird way where there's no single source of truth for what the number is at any given moment. It's in limbo between the time when the action is called finished...and that's no good.
Either store the number client side, and every time you add to it, add to that number directly and then tell the server side what the new number is... (i.e. the client side is taking responsibility as the source of truth for the number while the client side runs)
OR store the number server side, and every time you up it with an action from the client side, the server returns the new updated number. (i.e. the source of truth for the number is completely server side).
Then, even if race issues occur, you still have a source of truth for what the number is, and that source can be checked and confirmed. For example, if the server side holds the source of truth for the number then the API can also return a timestamp for the status of that value every time it returns it, and you can check it against the last value you got from the API to make sure you're ACTUALLY using the newest value.

node.js - determine when per-row asynchronous function is not called due to there being no rows

I'm trying to write code that will be executed if an SQLite query won't return results. However, the async nature of Node.js makes this difficult. I don't know if I can write code inside the callback function, because when I test with inputs that will cause empty results, nothing happens.
I'm doing
db.each("SELECT pid, collection , photo FROM photos WHERE collection = '"+collection_id+"' AND pid = '"+photo_id+"' ", function(err, row) {
console.log("PHOTO FOUND");
//code inside the callback function
});
//code I want
Specifically, I want to render something general, in case the user requests something that is not in the db
What should I do?
This is from the API documentation for the package you appear to be using (emphasis mine):
Database#each(sql, [param, ...], [callback], [complete])
Runs the SQL query with the specified parameters and calls the callback with for each result row. The function returns the Database object to allow for function chaining. The parameters are the same as the Database#run function, with the following differences:
The signature of the callback is function(err, row) {}. If the result set succeeds but is empty, the callback is never called. In all other cases, the callback is called once for every retrieved row. The order of calls correspond exactly to the order of rows in the result set.
After all row callbacks were called, the completion callback will be called if present. The first argument is an error object, and the second argument is the number of retrieved rows. If you specify only one function, it will be treated as row callback, if you specify two, the first (== second to last) function will be the row callback, the last function will be the completion callback.
If you know that a query only returns a very limited number of rows, it might be more convenient to use Database#all to retrieve all rows at once.
So, it would seem your code should look like this:
var sql = "SELECT pid, collection , photo FROM photos WHERE collection = '"+collection_id+"' AND pid = '"+photo_id+"' ";
db.each(sql, function(err, row) {
console.log("PHOTO FOUND");
//code inside the callback function
}, function(err, rows) {
if (rows == 0) {
//code I want
}
});

Resources