Hello Stackoverflow community,
In nuts
I am wondering why the insert callback is not being called async properly as the documentation says, having a code like:
Meteor.methods({
addUpdate: function (text) {
Updates.insert({
text: text,
createdAt: new Date(),
owner_email: Meteor.user().emails[0].address,
owner_username: Meteor.user().username
}, function(e, id) {
debugger; //<-- executed first with 'e' always undefined
});
debugger; //<-- executed after
}
});
the debugger inside the callback function is executed before the debugger afterwards, if the function is async the debugger inside the callback should be called at the end right?
More info
I am very new with meteor, the thing is that I am trying to make an small app, and experimenting, by now I wanted to confirm what I had understood about some concepts in this case the "insert" method. given the following code:
lib/collections/updateCollection.js
Update = function (params, id) {
params = params || {};
// define properties for update model such as text
this._text = params.text;
}
Update.prototype = {
// define some getters and setters, such as doc
get doc() {
return {
createdAt: this.createdAt,
text: this.text,
owner_email: this.owner_email,
owner_username: this.owner_username
};
},
notify: function notify(error, id) {
var client, notification, status;
client = Meteor.isClient ? window.Website : false;
notification = (client && window.Hub.update.addUpdate) || {}
status = (!error && notification.success) || notification.error;
if (client) {
return client.notify(status);
}
}
save: function save(callback) {
var that;
that = this;
callback = callback || this.notify;
Updates.insert(that.doc, function (error, _id) {
that._id = _id;
callback(error, _id); <-- here is the deal
});
}
}
lib/methods/updateService.js
updateService = {
add: function add(text) {
var update;
update = new Update({
text: text,
createdAt: new Date(),
owner_email: Meteor.user().emails[0].address,
owner_username: Meteor.user().username
});
update.save();
},
// methods to interact with the Update object
};
lib/methods/main/methods.js
Meteor.methods({
addUpdate: function (text) {
updateService.add(text);
}
});
My expectations here is when the client do something like Meteor.call('addUpdate', text); and everything is cool, a successful message is shown, otherwise the error is "truth" and an error message is shown. What is actually happening is that the callback is always called with error undefined (like if everything es cool), the callback also is not being called async, it is just called directly.
Even when I turn off the connection the update insertion shows a success message.
Any idea? maybe my app structure is making meteor work wrong? I really do not know. Thanks in advance.
Your code is executing inside a method. On the client, methods are executed simply to simulate what the server will do before the server responds (so that the app seems more responsive). Because the DB changes here are just simulating what the server is already doing, they are not sent to the server, and therefore synchronous. On the server, all code runs inside a Fiber, so it acts synchronous. (However, Fibers run in parallel just like normal callback-soup Node.)
Related
I'm trying to use the Steam Community (steamcommunity) npm package along with meteorhacks:npm Meteor package to retreive a user's inventory. My code is as follows:
lib/methods.js:
Meteor.methods({
getSteamInventory: function(steamId) {
// Check arguments for validity
check(steamId, String);
// Require Steam Community module
var SteamCommunity = Meteor.npmRequire('steamcommunity');
var community = new SteamCommunity();
// Get the inventory (730 = CSGO App ID, 2 = Valve Inventory Context)
var inventory = Async.runSync(function(done) {
community.getUserInventory(steamId, 730, 2, true, function(error, inventory, currency) {
done(error, inventory);
});
});
if (inventory.error) {
throw new Meteor.Error('steam-error', inventory.error);
} else {
return inventory.results;
}
}
});
client/views/inventory.js:
Template.Trade.helpers({
inventory: function() {
if (Meteor.user() && !Meteor.loggingIn()) {
var inventory;
Meteor.call('getSteamInventory', Meteor.user().services.steam.id, function(error, result) {
if (!error) {
inventory = result;
}
});
return inventory;
}
}
});
When trying to access the results of the call, nothing is displayed on the client or through the console.
I can add console.log(inventory) inside the callback of the community.getUserInventory function and receive the results on the server.
Relevant docs:
https://github.com/meteorhacks/npm
https://github.com/DoctorMcKay/node-steamcommunity/wiki/CSteamUser#getinventoryappid-contextid-tradableonly-callback
You have to use a reactive data source inside your inventory helper. Otherwise, Meteor doesn't know when to rerun it. You could create a ReactiveVar in the template:
Template.Trade.onCreated(function() {
this.inventory = new ReactiveVar;
});
In the helper, you establish a reactive dependency by getting its value:
Template.Trade.helpers({
inventory() {
return Template.instance().inventory.get();
}
});
Setting the value happens in the Meteor.call callback. You shouldn't call the method inside the helper, by the way. See David Weldon's blog post on common mistakes for details (section Overworked Helpers).
Meteor.call('getSteamInventory', …, function(error, result) {
if (! error) {
// Set the `template` variable in the closure of this handler function.
template.inventory.set(result);
}
});
I think the issue here is you're calling an async function inside your getSteamInventory Meteor method, and thus it will always try to return the result before you actually have the result from the community.getUserInventory call. Luckily, Meteor has WrapAsync for this case, so your method then simply becomes:
Meteor.methods({
getSteamInventory: function(steamId) {
// Check arguments for validity
check(steamId, String);
var community = new SteamCommunity();
var loadInventorySync = Meteor.wrapAsync(community.getUserInventory, community);
//pass in variables to getUserInventory
return loadInventorySync(steamId,730,2, false);
}
});
Note: I moved the SteamCommunity = Npm.require('SteamCommunity') to a global var, so that I wouldn't have to declare it every method call.
You can then just call this method on the client as you have already done in the way chris has outlined.
I want to use a time log in my database entries and I want that the time is always taken from the server.
So far I set this up:
Meteor.methods({
getTimeFromServer: function () {
var serverTime = new Date().toLocaleTimeString();
return serverTime;
}
});
I want to use them like this:
Template.myTemplate.events({
'click .btn': function(e) {
var propsOfObject = {
name: $('#idName').val(),
email: $('#idEmail').val(),
time: Meteor.call('getTimeFromServer')
}
Meteor.call('addItemToDataBase', propsOfObject)
}
});
Can't figure out what I am doing wrong. I don't get any error messages. The 'time'-property is simply empty.
First of all, your approach is not working because Meteor.call on the client is always asynchronous : it doesn't return anything immediately, you must provide a callback to fetch the result some time later.
More in this here : Meteor.methods returns undefined
I think you should get the server time directly in the insertion method instead of trying to fetch it from the server before inserting : it will save you a method call and it's simpler too.
client :
Template.myTemplate.events({
'click .btn': function(e) {
var propsOfObject = {
name: $('#idName').val(),
email: $('#idEmail').val()
};
Meteor.call('addItemToDataBase', propsOfObject)
}
});
server :
Meteor.methods({
addItemToDatabase:function(fields){
check(fields,{
name:String,
email:String
});
//
_.extend(fields,{
time:new Date().toLocaleTimeString()
});
//
MyCollection.insert(fields);
}
});
I can return a value if I send a sync message:
// frame script
var chromeBtnText = sendSyncMessage("getChromeToolbarButtonText");
if (chromeBtnText == 'blah') {
alert('tool is blah');
}
// chrome script
messageManager.addMessageListener("getChromeToolbarButtonText", listener);
function listener(message) {
return document.getElementById('myChromeToolbarButton').label.value;
}
How do I achieve this with a callback with sendAsyncMessage?
I was hoping to do something like:
// frame script
function myCallback(val) {
var chromeBtnText = val;
if (chromeBtnText == 'blah') {
alert('tool is blah');
}
}
var chromeBtnText = sendAsyncMessage("getChromeToolbarButtonText", null, myCallback);
There is no callback for replies. In fact, there is no reply at all. The return value from the chrome message listener is simply ignored for async messages.
To do fully async communication, you'd have to send another message containing the reply.
Frame script
addMessageListener("getChromeToolbarButtonTextReply", function(message) {
alert(message.data.btnText);
});
sendAsyncMessage("getChromeToolbarButtonText");
Chrome
messageManager.addMessageListener("getChromeToolbarButtonText", function(message) {
var btnText = document.getElementById('myChromeToolbarButton').label.value;
// Only send message to the frame script/message manager
// that actually asked for it.
message.target.messageManager.sendAsyncMessage(
"getChromeToolbarButtonTextReply",
{btnText: btnText}
);
});
PS: All messages share a namespace. So to avoid conflicts when another piece of code wants to use the same name getChromeToolbarButtonText, you better choose a more unique name in the first place, like prefixing your messages with your add-on name my-unique-addoon:getChromeToolbarButtonText or something like that. ;)
I was also hoping to do something similar:
messageManager.sendAsyncMessage("my-addon-framescript-message", null, myCallback);
I'm going the other direction so the myCallback would be in chrome but it's exactly the same principle.
I'd used similar approaches to #Noitidart and #nmaier before but in this new case I wanted to bind to some local data so myCallback can behave differently based on the application state at the time the first message was sent rather than at the time the callback is executed, all while allowing for the possibility of multiple message round-trips being in progress concurrently.
Chrome:
let someLocalState = { "hello": "world" };
let callbackName = "my-addon-somethingUnique"; // based on current state or maybe generate a UUID
let myCallback = function(message) {
messageManager.removeMessageListener(callbackName, myCallback);
//message.data.foo == "bar"
//someLocalState.hello == "world"
}.bind(this); // .bind(this) is optional but useful if the local state is attached to the current object
messageManager.addMessageListener(callbackName, myCallback);
messageManager.sendAsyncMessage("my-addon-framescript-message", { callbackName: callbackName } );
Framescript:
let messageHandler = function(message) {
let responseData = { foo: "bar" };
sendAsyncMessage(message.data.callbackName, responseData);
};
addMessageListener("my-addon-framescript-message", messageHandler);
There's a real-world example here: https://github.com/luckyrat/KeeFox/commit/c50f99033d2d07068140438816f8cc5e5e290da9
It should be possible for Firefox to be improved to encapsulate this functionality in the built-in messageManager one day but in the mean-time this approach works well and with a surprisingly small amount of boiler-plate code.
in this snippet below. i add the callback before sendAsyncMessage('my-addon-id#jetpack:getChromeToolbarbuttonText'... as i know it will send back. Then I remove it after callback executes. I know I don't have to but just to kind of make it act like real callback, just to kind of show people, maybe it helps someone understand.
Frame:
/////// frame script
function CALLBACK_getChromeToolbarButtonText(val) {
removeMessageListner('my-addon-id#jetpack:getChromeToolbarButtonTextCallbackMessage', CALLBACK_getChromeToolbarButtonText); //remove the callback
var chromeBtnText = val;
if (chromeBtnText == 'blah') {
alert('tool is blah');
}
}
addMessageListener('my-addon-id#jetpack:getChromeToolbarButtonTextCallbackMessage', CALLBACK_getChromeToolbarButtonText); //add the callback
var chromeBtnText = sendAsyncMessage("my-addon-id#jetpack:getChromeToolbarButtonText", null);
Chrome:
////// chrome script
messageManager.addMessageListener("my-addon-id#jetpack:getChromeToolbarButtonText", listener);
function listener() {
var val = document.getElementById('myChromeToolbarButton').label.value;
sendAsyncMessage('my-addon-id#jetpack:getChromeToolbarButtonTextCallbackMessage',val);
}
I am wondering why trying to run the following test suite fails when I try to delete the table I have stored entities in. The error I get is the following
1) Azure Storage cloud storage operations "after all" hook:
Error: The specified resource does not exist. RequestId:3745d709-fa5e-4a2b-b517-89edad3efdd2
Time:2013-12-03T22:26:39.5532356Z
If I comment out the actual insertion of data it fails every other time, and if I try to do the insertion of data it fails every time with an additional "The table specified does not exist.".
For the first case this seems to indicate that there is some kind of delay in the table creation, so in every other test it is successful, and for the second case it seems to indicate that even though my callbacks are being called after table creation, the table(s) still aren't ready for data insertion.
The test suite and associated code looks like this:
describe('cloud storage operations', function () {
var storage;
before(function (done) {
this.timeout(5000);
storage = AzureStorage.usingTable('TEST', done);
});
after(function (done) {
storage.deleteTable(done);
});
it('should store without trouble', function (done) {
storage.save(factory.createChangeSet()).then(done, done);
});
});
... // snipped from azure.js
var AzureStorage = function (storageClient, tableName, callback) {
assert(storageClient && tableName && partitionKey, "Missing parameters");
this.storageClient = storageClient;
this.tableName = tableName;
var defaultCallback = function (err) { if (err) { throw error; } };
this.storageClient.createTableIfNotExists(this.tableName, function () {
callback();
} || defaultCallback);
};
AzureStorage.usingTable = function (tableName, callback) {
return new AzureStorage(
azure.createTableService(accountName, accountKey)
, tableName
, callback
);
};
AzureStorage.prototype.deleteTable = function (callback) {
this.storageClient.deleteTable(this.tableName, callback);
};
I've hit this using the c# library as well but I'm pretty sure the error message indicated the table could not be created because an operation was still in process for a table of the same name. Thinking of the backend supporting storage, it makes sense that it would not be instant. The table needs to be removed from the 3 local replicas as well as the replicas in the paired data center.
With that kind of async operation, it is going to be challenging to build up an tear them down fast enough for tests.
A workaround might be to increment a value appended to the "TEST" table name that would be unique to that test run.
We are having a little problem with a functional test with casper.js.
We request the same resource twice, first with the GET and then with POST method.
Now when waiting for the second resource (POST) it matches the first resource and directly goes to the "then" function.
We would like to be able to check for the HTTP method in the "test" function, that way we can identify the resource properly. For now we use the status code (res.status), but that doesn't solve our problem fully, we really need the http method.
// create new email
this.click(xPath('//div[#id="tab-content"]//a[#class="button create"]'));
// GET
this.waitForResource('/some/resource',
function then() {
this.test.assertExists(xPath('//form[#id="email_edit_form"]'), 'Email edit form is there');
this.fill('form#email_edit_form', {
'email_entity[email]': 'test.bruce#im.com',
'email_entity[isMain]': 1
}, true);
// POST
this.waitForResource(
function test(res) {
return res.url.search('/some/resource') !== -1 && res.status === 201;
},
function then() {
this.test.assert(true, 'Email creation worked.');
},
function timeout() {
this.test.fail('Email creation did not work.');
}
);
},
function timeout() {
this.test.fail('Email adress creation form has not been loaded');
});
Or maybe there is a better way to test this scenario? Although since this is a functional test we need to keep all those steps in one test.
You can try to alter the form action url to add some query string, therefore generating a new resource appended to the stack. Could be done this way:
casper.thenEvaluate(function() {
var form = __utils__.findOne('#email_edit_form');
form.setAttribute('action', form.getAttribute('action') + '?plop');
});
That's a hack though, and functional testing should never be achieved that way. Let's hope more information will be added to the response objects in the future.
The res parameter that is passed to the test function has an ID. I created a helper that tests against this ID and blacklists it, so the same resource won't get accepted a second time.
var blackListedResourceIds = [],
testUniqueResource = function (resourceUrl, statusCode) {
return function (res) {
// check if resource was already loaded
var resourceFound = res.url.search(resourceUrl) !== -1;
// check statuscode
if (statusCode !== undefined) {
resourceFound = resourceFound && res.status === statusCode;
}
// check blacklisting
if (!resourceFound || blackListedResourceIds[res.id] !== undefined) {
return false;
} else {
blackListedResourceIds[res.id] = true;
return true;
}
};
};