Reputation: 13535
So i am having a bit of Dilemma, trying to refactor a set of nested anonymous closures in Javascript. However i am unsure the standard way of maintaining reference to parameters that otherwise would be seen by child closures.
for example
var foo;
var obj = new Obj();
obj.foobar(function (p1, p2, p3) {
foo.onEvent(function (s1, s2) {
if (s1 === 'bar') {
s2.status = p2; //p2 has a reference here
}
});
});
However if i refactor this code, so each closure it on its own named function, What would be the ideal way of getting a reference to p2?
example
var onFooEvent = function (s1, s2) {
if (s1 === 'bar') {
s2.status = p2; //p2 will no longer has a reference here
}
};
var onFoobar = function (p1, p2, p3) {
foo.onEvent(onFooEvent);
};
var foo;
var obj = new Obj();
obj.foobar(onFoobar);
MORE ROBUST EXAMPLE
var pooledQuery = function (query, params, env, res) {
return new imports.promise(function(resolve, reject) {
pool.acquire(function (err, client) {
var db;
if (err != null) {
console.log(err)
reject(err);
}
var meta = { env: env, res: res, result: [] };
db = client.query(query, params);
db.on('result', function (res) {
return res.on('row', function (row) {
return meta.result.push(row);
}).on('error', function (err) {
meta.result = err;
reject(meta);
}).on('end', function (info) {
return meta.result;
});
}).on('end', function () {
pool.release(client);
resolve(meta);
});
});
});
};
Upvotes: 1
Views: 328
Reputation: 1107
pass a curried function into foo.onEvent
(and additionally change the arguments that onFooEvent
accepts) -- something like this:
var onFooEvent = function (pArgs, s1, s2) {
if (s1 === 'bar') {
s2.status = pArgs[1];
}
};
var onFoobar = function (p1, p2, p3) {
foo.onEvent(_.curry(onFooEvent)(arguments));
};
here's a contrived runnable example:
var _ = require('lodash')
var print_stuff = function (p_args, s1, s2) {
console.log(p_args[0])
console.log(p_args[1])
console.log(p_args[2])
console.log(s1)
console.log(s2)
}
var do_theoretical_stuff = function (p1, p2, p3) {
return _.curry(print_stuff)(arguments)
}
var do_actual_stuff = do_theoretical_stuff(1, 2, 3)
do_actual_stuff('a', 'b')
which outputs to the console:
1
2
3
a
b
Update: really great point made below about bind
. the difference between bind
and _.curry
is that with bind you must set a context (via the first argument) at the time you use it. bind is basically doing two things for you: 1) setting context (i.e., explicitly binding/defining what this
will actually be inside the function now rather than when the function is invoked) and 2) doing what curry does. if you don't need or want the first feature, then i think a cleaner approach is to use curry. (if you don't normally use a library like lodash, it's easy to find a standalone curry function that can be added to one's local utils.) this SO question has more on the topic: Javascript 's bind vs curry?
Upvotes: 1
Reputation: 13535
here is how i solved in using bind
, there is bit more refactoring here where i plan to encapsulate all the functions inside another class instance. (However ignore that part) Just wanted to share how i solved it.
var DbPool = function () {
};
DbPool.prototype.onRow = function (meta, row) {
return meta.result.push(row);
};
var onRowError = function (meta, reject, err) {
meta.result = err;
reject(meta);
};
var onRowEnd = function (meta, info) {
return meta.result;
};
var onResult = function (meta, reject, res) {
var onRowBind = DbPool.prototype.onRow.bind(this, meta);
var onRowErrorBind = onRowError.bind(this, meta, reject);
var onRowEndBind = onRowEnd.bind(this, meta);
return res.on('row', onRowBind).on('error', onRowErrorBind).on('end', onRowEndBind);
};
var onEnd = function (meta, resolve, client) {
pool.release(client);
resolve(meta);
};
var dbPoolAcquired = function (query, params, env, res, resolve, reject, err, client) {
var db;
if (err != null) {
console.log(err)
reject(err);
}
var meta = { env: env, res: res, result: [] };
logger.debug("Query %s Params %s", query, params);
db = client.query(query, params);
var onResultBind = onResult.bind(this, meta, reject);
var onEndBind = onEnd.bind(this, meta, resolve, client);
db.on('result', onResultBind).on('end', onEndBind);
};
var dbPoolPromised = function(query, params, env, res, resolve, reject) {
var dbPoolAcquiredBind = dbPoolAcquired.bind(undefined, query, params, env, res, resolve, reject)
pool.acquire(dbPoolAcquiredBind);
};
var pooledQuery = function (query, params, env, res) {
var dbPoolPromisedBind = dbPoolPromised.bind(this, query, params, env, res);
return new imports.promise(dbPoolPromisedBind);
};
Upvotes: 1