DevZer0
DevZer0

Reputation: 13535

Refactoring: Anonymous functions to named functions, parameter reference

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

Answers (2)

user2524973
user2524973

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

DevZer0
DevZer0

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

Related Questions