JSDoc function returning a parameter of itself - javascript

I need to document a function returning conditionally one of its parameters. But JSDoc seems not to accept variables as a return value.
I tried to do something like this following the return type {1 | 0} logic
/**
* Finds the page with given index and returns
* - index if it does not exist
* - user if user doesn't have permissions to read
* - else the page itself
*
* #param {number} index
* #param {string} user
* #returns {index|user|Page}
*/
function getRead(index, user) {
let page = pages.find(page => page.details.index == index);
if (page && page.canRead(user)) {
return page;
} else if (!page) return index;
else return user;
}
But it is not recognized and the return values is just any, if i use {string|number|Page} as type, afterwards in my code I do something like this
if(page == index) return res.send(`Page ${index} does not exist`);
if(page == user) return res.send(`No permissions to view page ${index}`);
//page still recognized as number | string | Page
//no code completion or property suggestions on page
I also tried to add type checks to get there but i don't want to believe that like such a lazy the solution is the only one, there must be a better way to handle this
if(typeof page == "number" || page == index) return res.send(`Page ${index} does not exist`);
if(typeof page == "string" || page == user) return res.send(`No permissions to view page ${index}`);
//page recognized as Page

There are several solutions:
Use generic types for parameters
/**
* Finds the page with given index and returns
* - index if it does not exist
* - user if user doesn't have permissions to read
* - else the page itself
*
* #template {number} IndexType
* #template {string} UserType
* #param {IndexType} index
* #param {UserType} user
* #returns {IndexType|UserType|Page}
*/
function getRead(index, user) {
...
}
Remove JSDoc for the function at all and rely on TypeScript's type inference
function getRead(/**#type {number}*/index, /**#type {string}*/user) {
...
}

Related

Code intellisense for Javascript when function param is object

const _post = params => {
instance
.post(params.url, params.data)
.then(response => {
params.onSuccess(response);
})
.catch(error => {
params.onFailure(error);
});
};
So I have this helper code written by someone else in the team.
What I am looking for is when ever some tries to call this function, It should tell them that what properties it is expecting. Such as it is expecting three keys url, data, onSuccess, onFailure
How can we achieve that. I tried looking JS Doc specs but it only let us tell that the param is object like #param {Object} param
Try
/**
* some func.
* #param {Object} params - an object.
* #param {string} params.url - a url.
* #param {Object} params.data - an object.
* #param {Function} params.onSuccess .
* #param {Function} params.onFailure .
*/
function _post(params){}

How to strip comments in javascript?

I have multiple files that start with comments like:
/*
* #title Force email verification
* #overview Only allow access to users with verified emails.
* #gallery true
* #category access control
*
* This rule will only allow access users that have verified their emails.
*
* > Note: It might be a better UX to make this verification from your application.
*
* If you are using [Lock](https://auth0.com/docs/lock), the default behavior is to log in a user immediately after they have signed up.
* To prevent this from immediately displaying an error to the user, you can pass the following option to `lock.show()` or similar: `loginAfterSignup: false`.
*
* If you are using [auth0.js](https://auth0.com/docs/libraries/auth0js), the equivalent option is `auto_login: false`.
*
*/
//jshint -W025
function (user, context, callback) {
if (!user.email_verified) {
return callback(new UnauthorizedError('Please verify your email before logging in.'));
} else {
return callback(null, user, context);
}
}
All files contains two types of comments i.e /**/ and // Now I am reading this file in my javascript code and want to remove comments and get the actual code in the variable e.g
function (user, context, callback) {
if (!user.email_verified) {
return callback(new UnauthorizedError('Please verify your email before logging in.'));
} else {
return callback(null, user, context);
}
}
I have tried using strip-comments and parse-comments npm but none of these work. Here is the code:
const fs = require('fs');
const path = require('path');
const strip = require('strip-comments');
module.exports = function (ruleFileName, globals, stubs) {
globals = globals || {};
stubs = stubs || {};
const fileName = path.join(__dirname, '../src/rules', ruleFileName + '.js');
const data = fs.readFileSync(fileName, 'utf8');
const code = strip(data);
console.log(code);
return compile(code, globals, stubs);
}
and with parse-comments I tried like:
const parsed = parseComments(data)[0];
const code = data.split('\n').slice(parsed.comment.end).join('\n').trim();
I think strip comment is not working because it takes string as an argument but fs.readFileSync doesn't return string. I have also tried data.toString()but that also didn't work. So how can I strip comments from the content? Is there any other solution?
try use regx to replace /\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm
var Text = `/*
* #title Force email verification
* #overview Only allow access to users with verified emails.
* #gallery true
* #category access control
*
* This rule will only allow access users that have verified their emails.
*
* > Note: It might be a better UX to make this verification from your application.
*
* If you are using [Lock](https://auth0.com/docs/lock), the default behavior is to log in a user immediately after they have signed up.
* To prevent this from immediately displaying an error to the user, you can pass the following option to "lock.show()" or similar: "loginAfterSignup: false".
*
* If you are using [auth0.js](https://auth0.com/docs/libraries/auth0js), the equivalent option is "auto_login: false".
*
*/
//jshint -W025
function (user, context, callback) {
if (!user.email_verified) {
return callback(new UnauthorizedError('Please verify your email before logging in.'));
} else {
return callback(null, user, context);
}
}`
console.log(Text.replace(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm,''))
like this
https://codepen.io/anon/pen/eQKrWP

Selenium-webdriver JS - how to wait until an element is visible

Using selenium-webdriver (api docs here), how can you wait for an element to be visible?
I have the following functions, part of a home-made set of testing helpers, and the first one works but the second one fails (eg. it times out wating for and element to be visible even if it exists - as confirmed by the first function that works - and is visible - as confirmed by all imaginable tests and inspections of the page html/css/js).
Here they are:
/**
* Wait for an element to exist
*
* #param {object} locator
* #param {int} timeout (ms)
*
* #return {Promise<element>}
*/
// !! THIS WORKS OK
exports.waitForElement = function (locator, timeout) {
var waitTimeout = timeout || DEFAULT_TIMEOUT;
return this.wait(until.elementLocated(locator), waitTimeout)
.then(() => {
return this.findElement(locator);
});
};
/**
* Wait for an element to exist and then wait for it to be visible
*
* IMPORTANT: this is probable what you want to use instead of
* waitForVisibleElement most of the time.
*
* #param {hash} locator
* #param {number} timeout
*
* #return {Promise<element>}
*/
// !! THIS FAILS TO WORK AS EXPECTED
exports.waitForVisibleElement = function (locator, timeout) {
var waitTimeout = timeout || DEFAULT_TIMEOUT;
return this.waitForElement(locator, waitTimeout)
.then(el => {
console.log('--- element found:', el);
return this.wait(until.elementIsVisible(el), waitTimeout)
.then(() => {
console.log('--- element visible!');
// this is to make sure we are returning the same kind of
// promise as waitForElement
return this.findElement(locator);
});
});
};
...I tested in multiple contexts, so it's no other cause of the problem then the code inside waitForVisibleElement but I can't seem to find any reason for why it does not work!
As clarification, this for that code ends up being the webdriver instance (the result of new webdriver.Builder().withCapabilities(webdriver.Capabilities.chrome()).build()) after an augment method monkeypatches a given webdriver object... probably a questionable design pattern, but no the cause for my problem here :)
UPDATE: Apparently this only happens for XPath locators, like { xpath: '//*[contains(text(), "first name")]' }... not that it makes any more sense now. Also, it's the same for Firefox, so it's not a weird chrome-webdriver thingy...
It most likely is a Promise issue.
Try this instead:
exports.waitForElement = function (locator, timeout) {
var timeout = timeout || DEFAULT_TIMEOUT;
return this.wait(until.elementLocated(locator), timeout);
};
exports.waitForVisibleElement = function (locator, timeout) {
var timeout = timeout || DEFAULT_TIMEOUT;
var element = this.wait(until.elementLocated(locator), timeout);
return this.wait(new until.WebElementCondition('for element to be visible ' + locator, function() {
return element.isDisplayed().then(v => v ? element : null);
}), timeout);
};
Usage:
driver.get("...");
driver.waitForElement(By.id("..."), 2000).getText().then(function(text){
console.log(text);
});
driver.waitForVisibleElement(By.id("..."), 2000).getText().then(function(text){
console.log(text);
});

Returning a jQuery Promise in Revealing Module Pattern

I am writing a custom library in the Revealing Module pattern to handle a specific cookie and trying to use a jQuery Promise as a return for the cookie's "Getter" function in order to keep those calling the function from updating the function before it is initially set, thus keeping it synchronous.
See below:
/**
* Handles the state cookie for a browser.
*
* JS DEPENDENCIES:
* - jQuery Cookie plugin
*
* DOM DEPENDENCIES:
* - None
*
*/
var myUtilities = myUtilities || {};
myUtilities.stateManager = (function() {
var cookieName = 'us_state';
/**
* Find geolocation state / set cookie
* The passed deferred object only gets set as resolved if the AJAX response has the resulting data we need. Otherwise it is rejected.
*
* #param {Object} position Passed from 'navigator.geolocation.getCurrentPosition'. Contains browser's approximation of its current latitude+longitude.
* #return {Object} The promise resolution (resolve or reject). Resolved has a String of state abbreviation in lowecase. Rejected is empty.
*/
function _getLocation(position) {
var latitude = position.coords.latitude,
longitude = position.coords.longitude;
/* TEST VALUES */
/* CA coords */
// latitude = '37.7833';
// longitude = '-122.4167';
/* AZ coords */
// latitude = '33.45';
// longitude = '-112.0667';
// If this errors out due to CORS issue (or similar issue) of if the return value doesn't match then we set the promise to reject
return $.ajax({
url: 'https://maps.googleapis.com/maps/api/geocode/json?latlng=' + latitude + ',' + longitude,
dataType: "json"
});
}
/**
* Defer for getCurrentPosition callback
* Create an anonymous function to handle success; accepts a Position object as argument, and calls _getLocation() passing in the position object.
* When AJAX promise is complete evalute the data to find the state abbreviation.
* Reject a failed call for getCurrentPosition (user did not allow/timeout on browser's request to use geolocation)
*
* #var {Object} $df jQuery Deferred object
* #return {Object} jQuery Promise
*/
function _deferGetLocation() {
var $df = $.Deferred();
if ("geolocation" in navigator) {
navigator.geolocation.getCurrentPosition(
function(position) {
_getLocation(position)
.then(function(data) {
if (data.length !== 0) {
var result = data.results[0],
address = '',
state = '';
// A for-loop is used because the response changes based on the address that Google API returns (a single search into a specific part of the data Object is not always successful evne though the data may be in there)
for (var i = 0, len = result.address_components.length; i < len; i++) {
address = result.address_components[i];
if (address.types.indexOf('administrative_area_level_1') >= 0) {
// By returning here we exit the loop as soon as we get a match, like a 'break'
$df.resolve(address.short_name.toLowerCase());
break;
}
}
}
});
});
} else {
$df.reject();
}
return $df.promise();
}
/**
* Either get the get cookie or set it now.
* If the cookie exists we resolve the promise immediately, else wait for the geolocation to be resolved, set state cookie and resolve.
*
* #var {Object} $df jQuery Deferred object
* #var {String} stateString state, 2 character abbreviation format
* #return {Object} Promise with a String for the callback (two-character value indicating which state the user is in)
*/
function _getStateCookie(){
var $df = $.Deferred();
if ($.cookie(cookieName)) {
$df.resolve($.cookie(cookieName));
} else {
_deferGetLocation()
.then(function(state) {
$df.resolve(_setStateCookie(state));
});
}
return $df.promise();
}
/**
* Set the 'cookieName' cookie to a desired state, or default to 'co'
*
* #param {String} state The value of the cookie as a 2 character length state abbreviation
* #param {Datetime} expirationDate Days until the cookie expires
*/
function _setStateCookie (state, expirationDate){
state = ( typeof state == 'undefined' || !_isValidState(state) ) ? 'co' : state;
expirationDate = ( typeof expirationDate == 'undefined' ) ? 365 : expirationDate;
$.cookie(cookieName, state, { path: '/', expires: expirationDate });
// Offer an event listener for this cookie
$(document).trigger('state-utility.cookieChange');
return state;
}
/**
* Validates a given string against our predetermined "valid states" (AZ, CA, CA).
* Returns true if valid, false otherwise.
* Case-sensitive, AZ == az -> false
*
* #param {String} state A value to be compared for valid state
* #return {Boolean} True if valid, false otherwise
*/
function _isValidState(state) {
return (state == 'az' || state == 'ca' || state == 'ca');
}
function _isCookieSet() {
return ($.cookie(cookieName) && _isValidState($.cookie(cookieName)));
}
return {
// Using a Promise so that multiple calls to _getStateCookie() are handled synchronously
getStateCookie : function() {
return _getStateCookie().then( function(state) { return state; });
},
setStateCookie : function(state, expirationDate) {
return _setStateCookie(state, expirationDate);
},
updateStateElement : function(target) {
return _updateStateElement(target);
},
isValidState : function(state) {
return _isValidState(state);
},
isCookieSet : function() {
return _isCookieSet();
}
};
})();
<script src="https://raw.githubusercontent.com/carhartl/jquery-cookie/master/src/jquery.cookie.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
When the problem arises in trying to retrieve the cookie's value using myUtilities.stateManager.getStateCookie(). I expect this call to return with a two-character string for the nearest applicable state. Instead I get the Promise object returned.
Why does the Promise get returned instead of the string, and what needs to be changed in order to return the desired string?
Thank you for your time.
I'm afraid that you can't expect ever to derive a synchronous result from an asynchronous process in javascript. Nothing you do will ever convert async to sync. The best you can hope for (one day in the foreseeable future) is syntax that makes async code look more sync-like.
Here are some suggestions ...
In _getLocation(), I would :
add a fail handler to normalise jQuery.ajax's error reporting into a single reason.
function _getLocation(position) {
var latitude = position.coords.latitude,
longitude = position.coords.longitude;
return $.ajax({
url: 'https://maps.googleapis.com/maps/api/geocode/json?latlng=' + latitude + ',' + longitude,
dataType: "json"
}).then(null, function(jqXHR, textStatus, errorThrown) {
return errorThrown;
});
}
In _deferGetLocation(), I would :
purge the explicit promise construction antipattern from _deferGetLocation().
provide reasons for promise rejection under two consitions.
tidy slightly.
function _deferGetLocation() {
var promise;
if ("geolocation" in navigator) {
navigator.geolocation.getCurrentPosition(function(position) {
promise = _getLocation(position).then(function(data) {
var result = data.results[0],
state;
if (data.length !== 0) {
// A for-loop is used because the response changes based on the address that Google API returns (a single search into a specific part of the data Object is not always successful even though the data may be in there)
for (var i = 0, len = result.address_components.length; i < len; i++) {
if (result.address_components[i].types.indexOf('administrative_area_level_1') >= 0) {
state = result.address_components[i].short_name.toLowerCase();
break;
}
}
}
return state || $.Deferred().reject('geolocation failed').promise();
});
});
return promise || $.Deferred().reject('browser does not support geolocation').promise();
}
In the renamed _getStateCookie(), I would:
rename as _getStateCookieAsync() as a warning to consumers that the method returns a promise.
purge the explicit promise construction antipattern from _getStateCookie() and simplify (big time).
function _getStateCookieAsync() {
var state = $.cookie(cookieName);
return (state) ? $.when(state) : _deferGetLocation().then(_setStateCookie);
}
And in the method-exposing return statement I would :
expose only what's necessary - there's no obligation to expose every method.
expose by function name - no need for additional function wrappers.
return {
getStateCookieAsync : _getStateCookieAsync,
setStateCookie : _setStateCookie, // will it ever be set from outside?
// updateStateElement : _updateStateElement, // doesn't exist
isValidState : _isValidState, // probably only of use internally
isCookieSet : _isCookieSet
};
You only get a value out of promise by attaching a .then() handler and ALL .then() handlers are executed asynchronously.
So, this code simply does not work:
getStateCookie : function() {
return _getStateCookie().then( function(state) { return state; });
},
That is just going to return a promise who's value is state. Note, the .then() handler adds nothing to your code here.
Your result is async. You cannot change that. Callers must deal with it as async. That means the caller is going to get the result via a callback function. That callback can be a plain callback or a promise callback. Since you're already using promises, it's simplest to just return the promise that will have the value in it and let the caller put it's own .then() handler on it so it can get the value passed to it's own callback function.
I'd suggest this:
getStateCookie : function() {
return _getStateCookie();
},
And, then callers use it this way:
myUtilities.stateManager.getStateCookie().then(function(state) {
// callers code goes here to use state
});

Why variable assignation doesnt work as expected?

We have a collection of persons, and a collection of adresses. In each person, there's an id for the adress. We try do do a 'join-like' in JavaScript, and it's not possible to add new field in the return object.
var ret;
app.get('/tp/show/method', function (req, res) {
ret={};
User2Model.find(function(err, users) {
if(err){
console.error('Error find: '+err);
}
var last_id;
for(var user_id in users){
last_id=users[user_id]._id;
ret[last_id]=users[user_id];
}
for(var user_id in users){
AdrModel.find({ 'user_id': users[user_id]['id_adr'] },function(err, adr) {
if (err){
console.error('Error find: '+err);
}
for(var i in adr){
for(var user_id in users){
if (users[user_id].id_adr==adr[i].user_id) {
/* ok id found, so add all adresses to the result: */
/* The following line doesn't work: */
ret[users[user_id]._id]=adr;
if(users[user_id]._id==last_id){
var url_parts = url.parse(req.url, true);
var query = url_parts.query;
res.setHeader(
'content-type', 'application/javascript'
);
json=query.callback+'('+JSON.stringify({
data:{success: 1, value: ret }
})+')';
res.send(json);
}
break;
}
}
}
});
}
});
});
The variable ret is global, so we should be able to modify it, but the return result just accept when we override some of the properties already there. It doesn't work if we try to add new properties like "addr". What am I missing?
This is a typical problem caused by trying to handle asynchronous code with synchronous means. Your entire attempt is unfixable, you need to scrap it.
One widely adopted way of handling asynchronous code without going insane is by using promises.
Here is what your code could look like if you used a promise library. For the sake of the example I'm using Bluebird here.
var findAddress = Promise.promisify(AdrModel.find);
var findUsers = Promise.promisify(User2Model.find);
// a helper function that accepts a user object and resolves the address ID
function attachAddrToUser(user) {
return findAddress({
user_id: user.id_adr
}).then(function (address) {
user.address = address;
return user;
}).catch(function (e) {
console.error("error finding address for user ID " + user.id_user, e);
});
}
findUsers().then(function (users) {
var pending = [], id_user;
for (id_user in users) {
pending.push(attachAddrToUser(users[id_user]));
}
Promise.all(pending).then(function (users) {
// all done, build and send response JSON here
}).catch(function (e) {
// don't forget to add error handling
});
});
working jsFiddle over here: http://jsfiddle.net/Tomalak/2hdru6ma/
Note: attachAddrToUser() modifies the user object that you pass to it. This is not entirely clean, but it's effective in this context.
As I indicated in comments to #Tomalak's solution above, events can also be used to manage program flow in an async environment.
Here's an alternate partial solution that uses just that approach.
Please note that of the various ways of accomplishing this goal (I know of at least three, or four if you accept that the pain of "Callback Hell" can be ameliorated through the use of callbacks defined outside of and only referenced inline by their caller), I prefer using events since they are a more natural way for me to think about this class of problem.
Take-aways
Events are an efficient and easily understandable way to manage program flow in an async programming environment.
Rather than simple triggers, events can be used transport any data so they can be used further on for any purpose.
Events can easily call other events without worrying about scope.
Event processing allows you to unwind your code such that it becomes easier to track, and thus debug, as well as reducing the burden on the stack typically seen in deeply nested or recursive code. In other words, events are fast and very memory efficient.
Explanation
The code first defines two mocks:
an App class which provides a get method, allowing us to mock out the OP's app instance, and
a User2Model singleton that provides a find function for the same purpose.
It then documents the following events:
error - which is called on any errors to print a message to console and exit the program
get - which is fired with the result of the app.get method and immediately fires the processUsers event with {req:req,res:res}
processUsers - fired by the get event handler with a mocked array of user objects, sets up a results object and a last_id value, and then calls the nextUser event.
nextUser - fired by the processUsers event which picks the next user off the users array, sets evt.last_id, adds the user to the evt.results, and emits itself, or if there are no users left on the evt.users array, emits complete
complete - fired by nextUser and simply prints a message to console.
Event handlers are next defined using the 'on'+eventName convention.
And finally, we
define an eventHandlers object, to map handlers to their appropriate events,
instantiate our app instance, and
invoke its get method with a callback that simply emits a get event to start the ball rolling.
I've documented most of the solution using jsdoc and added logging messages to show progress as each event is emitted and its handler invoked. The result of the run is included after the code. (The http req and res objects have been commented out of the log messages for the sake of brevity.)
One final note, while this example is 269 lines long, most of it is documentation.
The actual code (without the mocks) is only about 20 or 25 lines.
Code
/*
Example of using events to orchestrate program flow in an async
environment.
*/
var util = require('util'),
EventEmitter = require('events').EventEmitter;
// mocks
/**
* Class for app object (MOCK)
* #constructor
* #augments EventEmitter
*/
var App = function (handlers) {
EventEmitter.call(this);
this.init(handlers);
};
util.inherits(App, EventEmitter);
/**
* Inits instance by setting event handlers
*
* #param {object} handlers
* #returns {App}
*/
App.prototype.init = function (handlers) {
var self = this;
// set event handlers
Object.keys(handlers).forEach(function (name) {
self.on(name, handlers[name]);
});
return self;
};
/**
* Invokes callback with req and res
* #param uri
* #param {App~getCallback} cb
*/
App.prototype.get = function (uri, cb) {
console.log('in app.get');
var req = {uri: uri},
res = {uri: uri};
/**
* #callback App~getCallback
* #param {object} req - http request
* #param {object} res - http response
* #fires {App#event:get}
*/
cb(req, res);
};
/**
* Data access adapter - (MOCK)
* #type {object}
*/
var User2Model = {};
/**
*
* #param {User2Model~findCallback} cb
*/
User2Model.find = function (cb) {
var err = null,
users = [
{_id: 1},
{_id: 2}
];
/**
* #callback User2Model~findCallback
* #param {Error} err
* #param {Array} users
*/
cb(err, users);
};
// events
/**
* Error event.
*
* #event App#error
* #type {object}
* #property {object} [req] - http request
* #property {object} [res] - http response
* #property {string} where - name of the function in which the error occurred
* #property {Error} err - the error object
*/
/**
* Get event - called with the result of app.get
*
* #event App#get
* #type {object}
* #property {object} req - http request
* #property {object} res - http response
*/
/**
* ProcessUsers event - called
*
* #event App#processUsers
* #type {object}
* #property {object} req - http request
* #property {object} res - http response
* #property {Array} users - users
*/
/**
* NextUser event.
*
* #event App#nextUser
* #type {object}
* #property {object} req - http request
* #property {object} res - http response
* #property {Array} users
* #property {*} last_id
* #property {object} result
*/
/**
* Complete event.
*
* #event App#complete
* #type {object}
* #property {object} req - http request
* #property {object} res - http response
* #property {Array} users
* #property {*} last_id
* #property {object} result
*/
// event handlers
/**
* Generic error handler
*
* #param {App#event:error} evt
*
* #listens App#error
*/
var onError = function (evt) {
console.error('program error in %s: %s', evt.where, evt.err);
process.exit(-1);
};
/**
* Event handler called with result of app.get
*
* #param {App#event:get} evt - the event object
*
* #listens App#appGet
* #fires App#error
* #fires App#processUsers
*/
var onGet = function (evt) {
console.log('in onGet');
var self = this;
User2Model.find(function (err, users) {
if (err) {
console.log('\tonGet emits an error');
return self.emit('error', {
res:evt.res,
req:evt.req,
where: 'User2Model.find',
err: err
});
}
self.emit('processUsers', {
//req:req,
//res:res,
users: users
});
});
};
/**
* Handler called to process users array returned from User2Model.find
*
* #param {App#event:processUsers} evt - event object
* #property {object} req - http request
* #property {object} res - http response
* #property {Array} users - array of Users
*
* #listens {App#event:processUsers}
* #fires {App#event:nextUser}
*/
var onProcessUsers = function (evt) {
console.log('in onProcessUsers: %s', util.inspect(evt));
var self = this;
evt.last_id = null;
evt.result = {};
self.emit('nextUser', evt);
};
/**
* Handler called to process a single user
*
* #param evt
* #property {Array} users
* #property {*} last_id
* #property {object} result
*
* #listens {App#event:nextUser}
* #emits {App#event:nextUser}
* #emits {App#event:complete}
*/
var onNextUser = function (evt) {
var self = this;
console.log('in onNextUser: %s', util.inspect(evt));
if (!(Array.isArray(evt.users) && evt.users.length > 0)) {
return self.emit('complete', evt);
}
var user = evt.users.shift();
evt.last_id = user._id;
evt.result[evt.last_id] = user;
self.emit('nextUser', evt);
};
/**
* Handler invoked when processing is complete.
*
* #param evt
* #property {Array} users
* #property {*} last_id
* #property {object} result
*/
var onComplete = function (evt) {
console.log('in onComplete: %s', util.inspect(evt));
};
// main entry point
var eventHandlers = { // map our handlers to events
error: onError,
get: onGet,
processUsers: onProcessUsers,
nextUser: onNextUser,
complete: onComplete
};
var app = new App(eventHandlers); // create our test runner.
app.get('/tp/show/method', function (req, res) { // and invoke it.
app.emit('get', {
req: req,
res: res
});
/* note:
For this example, req and res are added to the evt
but are ignored.
In a working application, they would be used to
return a result or an error, should the need arise,
via res.send().
*/
});
Result
in app.get
in onGet
in onProcessUsers: { users: [ { _id: 1 }, { _id: 2 } ] }
in onNextUser: { users: [ { _id: 1 }, { _id: 2 } ], last_id: null, result: {} }
in onNextUser: { users: [ { _id: 2 } ],
last_id: 1,
result: { '1': { _id: 1 } } }
in onNextUser: { users: [],
last_id: 2,
result: { '1': { _id: 1 }, '2': { _id: 2 } } }
in onComplete: { users: [],
last_id: 2,
result: { '1': { _id: 1 }, '2': { _id: 2 } } }
Well, I get it. If the function AdrModel.find is async, you're setting this values always to the last user.
This occurs because a async function will be executed after the for block end. So, the value of the user_id in all AdrModel.find calls will be always the same, because the saved scope where the async call is executed. Let's say your users are this collection
[{_id: 0}, {_id:2}, {_id: 3}]
So the calls of AdrModel.find will always use user_id -> 3 value:
ret[users[user_id]._id]=adr; //this guy will use user_id == 3, three times
EDIT
To resolve your problem is simple, modularize your code.
Create a function to do this resource gathering:
function setAdr(userId){
AdrModel.find({ 'user_id': userId },function(err, adr) {
...
}
}
And then, you call it in your 'for':
...
for(var user_id in users){
setAdr(users[user_id].id_adr);
...
This way you'll save a safe scope for each async call.

Categories