Meteor method does not return when passing a value - meteor

I am trying to pass a value in the callback of an async meteor method. "mongoCollections" is global variable
// Async method
let waiter = function(cb) {
setTimeout(() => {
cb(undefined, {data: 'test', other: mongoCollections})
}, 1000);
}
// Meteor method
Meteor.methods({
'getCollections': () => {
let func = Meteor.wrapAsync(waiter);
let res = func();
return res;
}
});
On the client
Meteor.call('getCollections', (err, res) => {
console.log(err, res)
});
The issue is that in its current state the client callback is not fired, no error or anything.
But if I remove the "other: mongoCollections" part of the object then the callback is fired. Why would sending mongoCollections prevent the callback from being fired at all? If there is an error how can I catch it?

My guess is that you are loosing your context between waiter() and the execution of cb(), which means that mongoCollections is undefined in cb(), thus the call fails.
Try to log mongoCollections in the anonymous function that you setTimeout for. It will probably show as undefined.
Or try it like this:
let waiter = function(cb) {
var _mongoCollections = mongoCollections;
setTimeout(() => {
cb(undefined, {data: 'test', other: _mongoCollections})
}, 1000);
}
(which puts mongoCollections in the closure instsead)
Another possibility (based on comment below): Your mongoCollections object is not serializable. You can try it by logging the result of JSON.stringify(mongoCollections). If this fails you have to extract the parts of the object you need, that can be serialized.

There are a number of things that could be happening here, but my guess is that an error is occurring somewhere and the error message is getting swallowed by a handler somewhere deeper.
You probably want to be using Meteor.setTimeout instead of vanilla setTimeout at a minimum. Have a look here: http://docs.meteor.com/api/timers.html#Meteor-setTimeout
Beyond that, I would follow the previous answerer's advice and try to make sure that mongoCollections is as global as you think it is. If the only change between the callback working and not working is the addition of a single symbol, then the culprit is likely that your added symbol is undefined.

Related

Why is setTimeout not pausing the appropriate milliseconds in my jest tests?

I'm playing around with the jest documentation on timer mocks to better understand how they work. When I run the below code, why are the setTimeout's not pausing? When I run this test, it immediately gives me test results even when I had set those gigantic millisecond values. For example 'b' prints immediately even when 500000000000 milliseconds has not passed.
timerGame-test.js
test('calls the callback after 1 second', () => {
const timerGame = require('../timerGame');
const callback = jest.fn();
timerGame(callback);
// At this point in time, the callback should not have been called yet
expect(callback).not.toBeCalled();
console.log('a ');
// Fast-forward until all timers have been executed
setTimeout(
console.log('b'), 500000000000);
// Now our callback should have been called!
setTimeout(expect(callback).toBeCalled(),7000000);
});
timerGame.js
// timerGame.js
'use strict';
function timerGame(callback) {
console.log('Ready....go!');
setTimeout(() => {
console.log("Time's up -- stop!");
callback && callback();
}, 10000000);
}
module.exports = timerGame;
setTimeout expects two arguments, first one being a function. When you call it like this:
setTimeout(console.log('b'), 500000000000);
console.log('b') is evaluated immediately and the first argument given to setTimeout is whatever is returned by console.log('b'), which is undefined.
This would work as you expect:
setTimeout(function(){ console.log('b'); }, 500000000000);
EDIT: Note that in timerGame.js, the setTimeout is used correctly, and a function is given to it as the first argument.
Maybe this will clarify it to you, imagine you first store the callback function in a variable and then pass it to the setTimeout function, like this. This is essentially what happens in timerGame.js:
let logFunc = function() {
console.log("Time's up -- stop!");
}
// typeof(logFunc) == 'function'
setTimeout(logFunc, 50000);
Compare it with what you have earlier:
let badLogFunc = console.log('b');
// typeof(badLogFunc) == 'undefined'
setTimeout(badLogFunc, 50000);

Calling then after a promise wrapped inside a function

I'm wrapping a promise with firebase inside a function and I would like to call it twice, and then do something once the second call is finished. Here is my code to make it easy to understand, I'm using firebase:
this.updateDB('abc');
this.updateDB('cde');
updateDB = (content) => {
firebase.database().ref('something').update(content)
}
If I called the database update just once I could use the 'then.' and the do something, but I have to call this function twice. How can I do something once both function calls finish?
I tried to research the question got more confused.
Would appreciate any guidelines
Thanks in advance
Since .update() can return a promise, you can use Promise.all() with both promises to know when they are both done:
Promise.all(this.updateDB('abc'), this.updateDB('cde')).then(() => {
console.log('both done here');
}).catch(err => {
console.log('an error in one or both updates occurred');
});
updateDB = (content) => {
// return promise
return firebase.database().ref('something').update(content);
}

Test an async PipeTransform

Context
I have a basic PipeTransform, expect the fact that it is async. Why? because I have my own i18n service (because of parsing, pluralization and other constraints, I did my own) and it returns a Promise<string>:
#Pipe({
name: "i18n",
pure: false
})
export class I18nPipe implements PipeTransform {
private done = false;
constructor(private i18n:I18n) {
}
value:string;
transform(value:string, args:I18nPipeArgs):string {
if(this.done){
return this.value;
}
if (args.plural) {
this.i18n.getPlural(args.key, args.plural, value, args.variables, args.domain).then((res) => {
this.value = res;
this.done = true;
});
}
this.i18n.get(args.key, value, args.variables, args.domain).then((res) => {
this.done = true;
this.value = res;
});
return this.value;
}
}
This pipe works well, because the only delayed call is the very first one (the I18nService uses lazy loading, it loads JSON data only if the key is not found, so basically, the first call will be delayed, the other ones are instant but still async).
Problem
I can't figure out how to test this pipe using Jasmine, since it is working inside a component I know it works, but the goal here is to get this fully tested using jasmine, this way I can add it to a CI routine.
The above test:
describe("Pipe test", () => {
it("can call I18n.get.", async(inject([I18n], (i18n:I18n) => {
let pipe = new I18nPipe(i18n);
expect(pipe.transform("nope", {key: 'test', domain: 'test domain'})).toBe("test value");
})));
});
Fails because since the result given by the I18nService is async, the returned value is undefined in a sync logic.
I18n Pipe test can call I18n.get. FAILED
Expected undefined to be 'test value'.
EDIT: One way to do it would be to use setTimeout but I want a prettier solution, to avoid adding setTimeout(myAssertion, 100) everywhere.
Use fakeAsync from #angular/core/testing. It allows you to call tick(), which will wait for all currently queued asynchronous tasks to complete before continuing. This gives the illusion of the actions being synchronous. Right after the call to tick() we can write our expectations.
import { fakeAsync, tick } from '#angular/core/testing';
it("can call I18n.get.", fakeAsync(inject([I18n], (i18n:I18n) => {
let pipe = new I18nPipe(i18n);
let result = pipe.transform("nope", {key: 'test', domain: 'test domain'});
tick();
expect(result).toBe("test value");
})));
So when should we use fakeAsync and when should we use async? This is the rule of thumb that I go by (most of the time). When we are making asynchronous calls inside the test, this is when we should use async. async allows to test to continue until all asynchronous calls are complete. For example
it('..', async(() => {
let service = new Servce();
service.doSomething().then(result => {
expect(result).toBe('hello');
});
});
In a non async test, the expectation would never occur, as the test would complete before the asynchronous resolution of the promise. With the call to async, the test gets wrapped in a zone, which keeps track of all asynchronous tasks, and waits for them to complete.
Use fakeAsync when the asynchronous behavior is outside the control of the test (like in your case is going on in the pipe). Here we can force/wait for it to complete with the call to tick(). tick can also be passed a millisecond delay to allow more time to pass if needed.
Another option is to mock the service and make it synchronous, as mentioned in this post. When unit testing, if your components in test are dependent on heavy logic in the service, then the component in test is at the mercy of that service working correctly, which kinda defeats the purpose of a "unit" test. Mocking makes sense in a lot of cases.

Meteor.connection._lastSessionId is undefined in onCreated

So I have this code guys
Template.mainLayout.onCreated(function () { //HERE
console.log("mainLayout created");
var context = FlowRouter.current();
// use context to access the URL state
console.log(context);
var visitedOne = context.path;
//getting the connID
var clientIp = headers.getClientIP(); // no need for this anymore
var clientConnId = Meteor.connection._lastSessionId; // HERE
console.log(clientIp);
console.log(clientConnId); //HERE
// console.log(Meteor.connection._lastSessionId);
Meteor.call("updateHistory", {clientIp,clientConnId,visitedOne}, function(error, result){
if(error){
console.log("error", error);
});
if(result){
}
});//Meteor.call
});
My problems are marked by the comments //HERE
Meteor.connection._lastSessionId returns undefined at onCreated event. However if I try to get on click event it works just fine. Why is this caused, what's a workaround for this?
You're attempting to log the session ID before the connection has received it. For example, wrap your call in a setTimeout:
...
setTimeout(() => {
console.log(Meteor.connection._lastSessionId);
}, 500);
...
You might have to tweak the timeout value a bit, but it will be logged. Using setTimeout in this fashion really isn't that reliable though, as the amount of time it takes for the session ID to get set can vary. You'll likely want to look into setting up some kind of simple polling to continuously check for the session ID, until it's set.
Basically _lastSessionId isn't yet available on the client when the template is originally created (it's probably the first template rendered in your app). However there is no need to get this on the client since you're calling a server method anyway, just use the variable directly there where it will already exist!
So simplify:
Meteor.call("updateHistory", {clientIp,clientConnId,visitedOne}, callback)
to:
Meteor.call("updateHistory", visitedOne, callback)
and get the clientIp (if necessary) and use this.connection.id on the server.

Load data using Meteor.call on load

When trying to render my template, i want to load the data from the server. I'm trying to use Meteor.call but as per the documentation, i'm clearly not in a stub.
If I use Meteor.call inside of an event handler, the response i get back is correct. If i call it within the template.created or similar, i get an undefined response. I guess i could use async call to do it and then render it when available. But is there another way?
I don't want the clients to have direct access to the DB, i want it to come from the server.
//This doesn't work
Template.config.created = function() {
console.log(Meteor.call('getValue')); //returns undefined
};
//This works
Template.config.events({
'blur #button' : function () {
console.log(Meteor.call('getValue')); //Prints value
}
Any clues?
D
You need to use a callback in your Meteor.call
Template.config.created = function() {
Meteor.call('getValue', function(error, data) {
if(error){
//do stuff to handle error
}
console.log(data);
});
};
From the docs:
On the client, if you do not pass a callback and you are not inside a stub, call will return undefined, and you will have no way to get the return value of the method. That is because the client doesn't have fibers, so there is not actually any way it can block on the remote execution of a method.
I'm not sure why your event handler call is working... There isn't any way to synchronously get a server response like that in JavaScript without Fibers. The solution is simply to provide an asynchronous callback. This isn't really a Meteor limitation, it's just a JavaScript limitation.

Resources