make async.waterfall start with argument from another function - javascript

I have bumped into a problem that I can't seem to solve. This is for a steam trading bot and it works well except for when two people trades with it at the same time because class_id and other_id are global variables and they will change in the middle of a trade if more than one is using it.
I tried defining the variables inside the last if statement but then get_class_id did not find the variables. Is there any way the async function can take item.classid and convert_id_64(offer.accountid_other) directly without defining them as variables? I appreciate any help.
var class_id
var other_id
function accept_all_trades(offers_recieved) {
offers_recieved.forEach( function(offer) {
if (offer.trade_offer_state == 1) {
if (typeof offer.items_to_give === "accept") {
offers.acceptOffer({tradeOfferId: offer.tradeofferid}, function(error, response) {
console.log('accepterat offer');
offer.items_to_receive.forEach(function(item) {
if (item.appid === '420') {
class_id = item.classid;
other_id = convert_id_64(offer.accountid_other);
console.log(class_id);
async.waterfall([get_class_id, get_stack, get_name, get_save], add_names);
}
});
});
}
}
});
}
function get_class_id(callback) {
var test = class_id
callback(null, test)
}
Update
I've changed the code to what ben suggested but still when I call get_class_id and try to print the id it is just a blank row in the console, any Ideas?
function get_class_id(callback) {
console.log(class_id);
var test = class_id;
callback(null, test)
}

The problem here is not aysnc.waterfall(). It's async calls (offers.acceptOffer(), get_class_id, get_stack, get_name, get_save, add_names) inside regular javascript forEach(). You need control-flow loops that can control the flow of those async calls. Here is the revised code using async.each():
function accept_all_trades(offers_recieved) {
async.each(offers_recieved, function(offer, eachCb) {
if (offer.trade_offer_state !== 1 || typeof offer.items_to_give !== "accept") {
return eachCb(null);
}
offers.acceptOffer({tradeOfferId: offer.tradeofferid}, function(error, response) {
console.log('accepterat offer');
async.each(offer.items_to_receive, function(item, eachCb) {
var class_id;
var other_id;
if (item.appid !== '420') {
return eachCb(null);
}
class_id = item.classid;
other_id = convert_id_64(offer.accountid_other);
console.log(class_id);
async.waterfall([
function(waterfallCb) {
var test = class_id;
console.log(class_id);
waterfallCb(null, test);
},
get_stack,
get_name,
get_save,
add_names
], eachCb);
}, function(err) {
console.log(err);
eachCb(null);
});
});
});
}

Related

Parse Cloud Code: Logic Branching in Promises

I'm trying to write a Parse.com Cloud Code function to accomplish the following workflow:
User submits a value.
Cloud code function checks to see if that value matches any objects of type code.
If not, the function returns a "not found" value.
If so, the object of type code is assumed to have a pointer to an object of type item.
Then, code.item is checked to see whether it has a pointer to an object of type alert.
If not, the function returns a "not found" value.
If code.item.alert does exist, then I want to fetch the full alert object, including pointers which may or may not exist, up to 2 layers deep.
As I begin writing the code for this function, I can get it working to the point of checking to see whether the code exists and, if so, whether code.item.alert also exists.
This is where the problem arises. As it currently stands, in the working version of my function, the alert item that is returned is only the class type and objectId. I understand why that is happening, and I am trying to write code to populate the object before returning it, but I am failing in that attempt.
Here's the code that is working so far (but only returning the alert object's shell):
Parse.Cloud.define("alertLookup", function (request, response) {
Parse.Cloud.useMasterKey();
var codeQuery = new Parse.Query("code");
codeQuery.equalTo("value", request.params.code);
codeQuery.include("item");
codeQuery.find().then(function (codes) {
if (codes.length === 0) {
response.success("no item");
} else {
var code = codes[0];
var item = code.get("item");
var alert = item.get("alert");
if (alert === null || alert === undefined) {
response.success("no item");
} else {
response.success(alert);
}
}
}, function (error) {
response.error(error);
});
});
Here's what I have tried that is failing with an error code of 141:
Parse.Cloud.define("alertLookup", function (request, response) {
Parse.Cloud.useMasterKey();
var codeQuery = new Parse.Query("code");
codeQuery.equalTo("value", request.params.code);
codeQuery.include("item");
codeQuery.find().then(function (codes) {
if (codes.length === 0) {
response.success("no item");
} else {
var code = codes[0];
var item = code.get("item");
var alert = item.get("alert");
if (alert === null || alert === undefined) {
response.success("no item");
} else {
return alert.fetch();
}
}
}).then(function (a) {
response.success(a);
}, function (error) {
response.error(error);
});
});
Why won't the fetch() call work properly? When I insert console.log() statements, although alert is non-null, return alert.fetch(); does not ever seem to be called. At least, the response.success(a); line is never called. Why not?
Try this instead while chaining Promises:
codeQuery.find().then(function (codes) {
if (codes.length != 0) {
var code = codes[0];
var item = code.get("item");
var alert = item.get("alert");
if (alert != null && alert != undefined) {
var alertObj = new Parse.Object("alert"); // alert class ???
alertObj.id = alert.id;
return alertObj.fetch();
}
}
// return a Promise for no items
return Parse.Promise.as("no item");
}).then(function (a) {
response.success(a);
}, function (error) {
response.error(error);
});

variable may not have been initialized when test

I'm trying to understand why I get "variable may not have been initialized" when I test (variable === "some text") but I dont get when I used (typeof passwordHashOrg !== 'undefined')
The working code:
checkPass('share', function (error, response) {
"use strict";
if (error) {
console.log(error);
} else {
console.log(response);
}
});
function checkPass(username, callback) {
"use strict";
// Require
var fs = require('fs');
// Static Parameters
var usernameInput = username;
// Open shadow file
fs.readFile('/etc/shadow', function (error, file) {
if (error) {
return callback(error); // file does not exit
}
// file is a buffer, convert to string and then to array
var shadowArray = file.toString().split('\n');
var passwordHashOrg;
shadowArray.forEach(function (line) {
var shadowLineArray = line.split(":");
var usernameOrg = shadowLineArray[0];
if (usernameOrg === usernameInput) {
passwordHashOrg = shadowLineArray[1];
}
});
if (typeof passwordHashOrg !== 'undefined') {
callback(null, 'userExist');
} else {
callback(null, 'unknownUser');
}
});
}
and the code that I get "variable may not have been initialized" :
checkPass('share', function (error, response) {
"use strict";
if (error) {
console.log(error);
} else {
console.log(response);
}
});
function checkPass(username, callback) {
"use strict";
// Require
var fs = require('fs');
// Static Parameters
var usernameInput = username;
// Open shadow file
fs.readFile('/etc/shadow', function (error, file) {
if (error) {
return callback(error); // file does not exit
}
// file is a buffer, convert to string and then to array
var shadowArray = file.toString().split('\n');
var passwordHashOrg;
shadowArray.forEach(function (line) {
var shadowLineArray = line.split(":");
var usernameOrg = shadowLineArray[0];
if (usernameOrg === usernameInput) {
passwordHashOrg = shadowLineArray[1];
}
});
if (passwordHashOrg === 'some text') {
callback(null, 'userExist');
} else {
callback(null, 'unknownUser');
}
});
}
The only diferent from the two code is:
if (typeof passwordHashOrg !== 'undefined') {
vs
if (passwordHashOrg === "some text") {
The warning means just what it says - the variable might not have been initialised with a value at the time of the comparison. Since that's the expected behaviour, and basically just what you want to test for, the warning can be safely ignored. Alternatively, use
var passwordHashOrg = null; // or
var passwordHashOrg = undefined; // or whatever default value you want
So why didn't you get the warning when using typeof? Because that's not necessarily evaluating the value of the variable, and might be seen by jshlint as justified on non-initialised variables (it even works on undeclared variables). You'd probably get the same warning if you did compare the value to undefined.

Async Recursion with JavaScript and Node.js

This is probably a noob JavaScript question, but I'm looking to know if my solution to a problem I am having is 'correct'
I have created the following sample application that recreates my error:
Firstly in index.js
var processor = require('./fileProcessor/processor.js');
var container = {
source: "source.txt",
destination: "destination.txt"
};
new processor().process(container);
I create my container object which has the name of the source file and the name of the destination file. This is passed into the process function of the processor:
var fileProcessor = require('./fileProcessor.js');
module.exports = function Processor() {
this.process = function(container) {
var file = new fileProcessor();
if(container.finished === undefined) {
if(container.body === undefined) {
file.read(container, this.process);
} else {
file.write(container, this.process);
}
}
};
};
As you can see this calls the read and write functions passing in the container and the process function as the callback, the fileProcessor looks like this:
var fs = require('fs');
module.exports = function() {
this.read = function(container, callback) {
fs.readFile(container.source, function (err, data) {
if(err) throw err;
container.body = data;
callback(container);
});
};
this.write = function(container, callback) {
fs.writeFile(container.destination, container.body, function(err) {
if(err) {
return console.log(err);
}
container.finished = true;
callback(container);
});
};
};
In simple terms the processor calls file.read, which reads the file and calls back into the process function, which then calls the write function. However at the end of the write function an error is thrown:
callback(container);
^
TypeError: object is not a function
Obviously when passing in this.process to file.write(container, this.process); the this isn't the this I intend it to be!
If I update my processor by adding a processFunction variable:
var fileProcessor = require('./fileProcessor.js');
module.exports = function Processor() {
var processFunction = function(container) {
var file = new fileProcessor();
if(container.finished === undefined) {
if(container.body === undefined) {
file.read(container, processFunction);
} else {
file.write(container, processFunction);
}
}
};
this.process = function(container) {
processFunction(container);
};
};
Everything works fine. Is this a good way to do this or is there a better solution?
I think this is a fine way to do it. There is one possible modification that you might make. Since you are creating a new name in your scope just for the purpose of recursing, you could just name your function and refer to it by its name inside of the function.
module.exports = function Processor() {
this.process = function processFunction(container) {
var file = new fileProcessor();
if(container.finished === undefined) {
if(container.body === undefined) {
file.read(container, processFunction);
} else {
file.write(container, processFunction);
}
}
};
};
Then you can avoid creating a name (processFunction) that will be visible outside the function.
Take a look here for reference:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/function#Named_function_expression

Unreliable behaviour in Node.js

I have a Node.js application that, upon initialisation, reads two tables from an SQL database and reconstructs their relationship in memory. They're used for synchronously looking up data that changes (very) infrequently.
Problem: Sometimes I can't access the data, even though the application reports successfully loading it.
Code:
constants.js
module.exports = {
ready: function () { return false; }
};
var log = sysLog('core', 'constants')
, Geo = require('../models/geo.js');
var _ready = false
, _countries = []
, _carriers = [];
function reload() {
_ready = false;
var index = Object.create(null);
return Geo.Country.find().map(function (country) {
var obj = country.toPlainObject()
, id = obj.id;
delete obj.id;
index[id] = obj;
return Object.freeze(obj);
}).then(function (countries) {
log.debug('Loaded ' + countries.length + ' countries');
_countries = countries;
return Geo.Carrier.Descriptor.find().map(function (carrier) {
var obj = carrier.toPlainObject();
if (obj.country) {
obj.country = index[obj.country];
}
return Object.freeze(obj);
}).then(function (carriers) {
log.debug('Loaded ' + carriers.length + ' carriers');
_carriers = carriers;
});
}).finally(function () {
_ready = true;
});
}
reload().catch(function (err) {
log.crit({ message: 'Could not load constants', reason: err });
process.exit(-42);
}).done();
module.exports = {
reload : reload,
ready : function () { return _ready; },
countries : function () { return _countries; },
carriers : function () { return _carriers; }
};
utils.js
var log = sysLog('core', 'utils')
, constants = require('./constants');
module.exports = {
getCountryByISO: function(iso) {
if (!iso) {
return;
}
if ('string' != typeof iso) {
throw new Error('getCountryByISO requires a string');
}
if (!constants.ready()) {
throw new UnavailableError('Try again in a few seconds');
}
switch (iso.length) {
case 2:
return _.findWhere(constants.countries(), { 'iso2' : iso.toUpperCase() });
case 3:
return _.findWhere(constants.countries(), { 'iso3' : iso.toUpperCase() });
default:
throw new Error('getCountryByISO requires a 2 or 3 letter ISO code');
}
},
getCarrierByCode: function(code) {
if (!code) {
return;
}
if ('string' != typeof code) {
throw new Error('getCarrierByCode requires a string');
}
if (!constants.ready()) {
throw new UnavailableError('Try again in a few seconds');
}
return _.findWhere(constants.carriers(), { 'code' : code });
},
getCarrierByHandle: function(handle) {
if (!handle) {
return;
}
if ('string' != typeof handle) {
throw new Error('getCarrierByHandle requires a string');
}
if (!constants.ready()) {
throw new UnavailableError('Try again in a few seconds');
}
return _.findWhere(constants.carriers(), { 'handle' : handle });
}
};
Use case
if (data.handle) {
carrier = utils.getCarrierByHandle(data.handle);
if (_.isEmpty(carrier)) {
throw new InternalError('Unknown carrier', { handle: data.handle });
}
}
What's going on: All errors are logged; as soon as I see an error (i.e. "Unknown carrier") in the logs, I check the SQL database to see if it should've been recognised. That has always been the case so far, so I check the debug log to see if data was loaded. I always see "Loaded X countries" and "Loaded Y carriers" with correct values and no sign of "Could not load constants" or any other kind of trouble.
This happens around 10% of the time I start the application and the problem persists (i.e. didn't seem to go away after 12+ hours) and seems to occur regardless of input, leading me to think that the data isn't referenced correctly.
Questions:
Is there something wrong in constants.js or am I doing something very obviously wrong? I've tried setting it up for cyclical loading (even though I am not aware of that happening in this case).
Why can't I (sometimes) access my data?
What can I do to figure out what's wrong?
Is there any way I can work around this? Is there anything else I could to achieve the desired behaviour? Hard-coding the data in constants.js is excluded.
Additional information:
constants.reload() is never actually called from outside of constants.js.
constants.js is required only in utils.js.
utils.js is required in app.js (application entry); all files required before it do not require it.
SQL access is done through an in-house library built on top of knex.js and bluebird; so far it's been very stable.
Versions:
Node.js v0.10.33
underscore 1.7.0
bluebird 2.3.11
knex 0.6.22
}).finally(function () {
_ready = true;
});
Code in a finally will always get called, regardless of if an error was thrown up the promise chain. Additionally, your reload().catch(/* ... */) clause will never be reached, because finally swallows the error.
Geo.Country.find() or Geo.Carrier.Descriptor.find() could throw an error, and _ready would still be set to true, and the problem of your countries and carriers not being set would persist.
This problem would not have occurred if you had designed your system without a ready call, as I described in my previous post. Hopefully this informs you that the issue here is really beyond finally swallowing a catch. The real issue is relying on side-effects; the modification of free variables results in brittle systems, especially when asynchrony is involved. I highly recommend against it.
Try this
var log = sysLog('core', 'constants');
var Geo = require('../models/geo.js');
var index;
var _countries;
var _carriers;
function reload() {
index = Object.create(null);
_countries = Geo.Country.find().map(function (country) {
var obj = country.toPlainObject();
var id = obj.id;
delete obj.id;
index[id] = obj;
return Object.freeze(obj);
});
_carriers = _countries.then(function(countries) {
return Geo.Carrier.Descriptor.find().map(function (carrier) {
var obj = carrier.toPlainObject();
if (obj.country) {
obj.country = index[obj.country];
}
return Object.freeze(obj);
});
});
return _carriers;
}
reload().done();
module.exports = {
reload : reload,
countries : function () { return _countries; },
carriers : function () { return _carriers; }
};
constants.reload() is never actually called from outside of
constants.js.
That's your issue. constants.reload() reads from a database, which is an aysnchronous process. Node's require() is a synchronous process. At the time constants.js is required in utils.js and the module.exports value is returned, your database query is still running. And at whatever point in time that app.js reaches the point where it calls a method from the utils module, that query could still be running, resulting in the error.
You could say that requiring utils.js has the side-effect of requiring constants.js, which has the side-effect of executing a database query, which has the side-effect of concurrently modifying the free variables _countries and _carriers.
Initialize _countries and _carriers as unresolved promises. Have reload() resolve them. Make the utils.js api async.
promises.js:
// ...
var Promise = require('bluebird');
var countriesResolve
, carriersResolve;
var _ready = false
, _countries = new Promise(function (resolve) {
countriesResolve = resolve;
})
, _carriers = new Promise(function (resolve) {
carriersResolve = resolve;
});
function reload() {
_ready = false;
var index = Object.create(null);
return Geo.Country.find().map(function (country) {
// ...
}).then(function (countries) {
log.debug('Loaded ' + countries.length + ' countries');
countriesResolve(countries);
return Geo.Carrier.Descriptor.find().map(function (carrier) {
// ...
}).then(function (carriers) {
log.debug('Loaded ' + carriers.length + ' carriers');
carriersResolve(carriers);
});
}).finally(function () {
_ready = true;
});
}
reload().catch(function (err) {
log.crit({ message: 'Could not load constants', reason: err });
process.exit(-42);
}).done();
module.exports = {
reload : reload,
ready : function () { return _ready; },
countries : function () { return _countries; },
carriers : function () { return _carriers; }
};
utils.js
getCarrierByHandle: function(handle) {
// ...
return constants.carriers().then(function (carriers) {
return _.findWhere(carriers, { 'handle' : handle });
});
}
Use case:
utils.getCarrierByHandle(data.handle).then(function (carrier) {
if (_.isEmpty(carrier)) {
throw new InternalError('Unknown carrier', { handle: data.handle });
}
}).then(function () {
// ... next step in application logic
});
This design will also eliminate the need for a ready method.
Alternatively, you could call constants.reload() on initialization and hang all possibly-dependent operations until it completes. This approach would also obsolete the ready method.
What can I do to figure out what's wrong?
You could have analyzed your logs and observed that "Loaded X countries" and "Loaded Y carriers" were sometimes written after "Unknown carrier", helping you realize that the success of utils.getCarrierByHandle() was a race condition.

Javascript closure and events

I am facing following issue:
I am calling in foreach cycle following browse function. When the rb.wsc.browse(symbol) is called the program do some WebSocket request and when the message is returned the event is emmited. The problem is that I always get the same browseData even when I know that the event is emited with different one. I think that this is closure issue, but I don't know how to solve it.
function browse(rb, symbol, callback) {
var result = function(wsc, browseData) {
wsc.off('browse', result);
wsc.off('failed', result);
var err = null;
if (wsc.errno < 0) {
err = new Error("Browsing symbol " + symbol + " failed!");
err.status = wsc.errno;
} else {
saveBrowseData(rb, browseData);
}
callback(err, symbol);
};
// Register temporary listeners
rb.wsc.on('browse', result);
rb.wsc.on('failed', result);
// Browse symbol
rb.wsc.browse(symbol);
}
RexBrowser.prototype.refresh = function() {
var that = this;
var browseRequestNumber = 1;
var browseResult = function(err, symbol) {
browseRequestNumber--;
var item = that.getSymbol(symbol);
_.each(item.children, function(child) {
if (child.browse) {
browseRequestNumber++;
debug("Browsing: " + child.cstring);
browse(that,child.cstring, browseResult);
}
});
if (browseRequestNumber === 0) {
that.emit('refresh', that);
}
};
// Start recursive browsing
browse(this,'$', browseResult);
};-
You could try using a IIFE:
} else {
function(rbInner, browseDataInner){
saveBrowseData(rbInner, browseDataInner);
}(rb, browseData);
}
This makes sure the variables used by / in saveBrowseData have the values they have when the function is called.

Categories