I’m trying to pass a bound method to a function, but I can’t quite figure out the syntax. It seems like javascript wants to somehow differentiate between an unbound method and a bound method, but where the calling context changes that. (Most programming languages simply define a method as a bound class function).
What’s the syntax for passing a bound method to a function, and then calling that bound method in the new scope?
Here’s the relevant snippets
const mongoose = require('mongoose');
const caching = require('../lib/caching')
const Blog = mongoose.model('Blog');
// This is where I'm trying to pass a method to another function. I'm unclear of the syntax here
Blog.find = caching.makeCachable(Blog.find.bind(Blog), Blog)
module.exports = app => {
app.get('/api/blogs', requireLogin, async (req, res) => {
let blogs = await Blog.find({_user: req.user.id});
return res.send(blogs);
});
// ... other routes
}
caching.js
const util = require('util');
const redis = require('redis');
const client = redis.createClient('redis://localhost:6379');
const asyncGet = util.promisify(client.get).bind(client);
const DEFAULT_CACHING = [
'EX', 60 * 60 * 1,//caching expires after 4 hours
]
// This is where I take the method and pass it on to another function. I hope this just passes through
function makeCachable(method, thisObject) {
console.log(method, thisObject, `${method.className}.${method.name}`);
return cachedQuery.bind(method, `${thisObject.className}.${method.name}`);
}
async function cachedQuery(queryFunction, queryKey, queryParams=null, cacheConfig=DEFAULT_CACHING) {
//check redis before executing queryFunction
const redisKey = JSON.stringify([queryKey, queryParams]);
const cacheValue = await asyncGet(redisKey);
if(cacheValue) {
return JSON.parse(cacheValue);
}
// This is where I try to call the bound method
const blogs = await queryFunction.call(queryParams);
if(blogs) {
client.set(redisKey, JSON.stringify(blogs), ...cacheConfig);
}
return blogs;
}
exports.makeCachable = makeCachable;
EDIT
Apparently I wasn’t clear enough about what doesn’t work. This version of my code is after trying several combinations that don’t work, but the error is always similar. For example, if I try to invoke const blogs = await queryFunction(queryParams);
I get the following error
[0] E:DocumentsCodenodejs_classAdvancedNodeStarterlibcaching.js:23
[0] const blogs = await queryFunction(queryParams);
[0] ^
[0]
[0] TypeError: queryFunction is not a function
[0] at Function.cachedQuery (E:DocumentsCodenodejs_classAdvancedNodeStarterlibcaching.js:23:25)
[0] at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
[0] at async E:DocumentsCodenodejs_classAdvancedNodeStarterroutesblogRoutes.js:22:17
[0]
Similarly, I’ve tried, Blog.find = caching.makeCachable(Blog.find);
, Blog.find = caching.makeCachable(Blog.find.bind(Blog));
, I’ve tried invoking with .call()
and without .call()
. In all cases, I get some kind of error like the one above.
What I would expect from all other programming languages, with some slight syntactic differences is that Blog.find is a bound method. For example, C++ would let you reference &Blog.find
or &Model::find
for bound/unbound methods, respectively. Python lets you simply refer to Blog.find
or Model.find
for bound/unbound methods, respectively.
It seems in some situations javascript expects you to explicitly bind it. But for whatever reason, when I pass the function, bound or not, to makeCachable it seems to stop being a function. What is the actual syntax for passing a bound method to another function? Is there something extra that needs to be done to bind a method to another function parameter?
2
Answers
Ok, I still don't know why, but here's a workaround. If I just change the makeCacheable function to use a closure instead of invoking bind explicitly, it works
I don't have an explanation for why
return cachedQuery.bind(method, queryKey);
doesn't work, but the closure does work..bind
accepts one or more arguments:this
value within the functionWhere you are doing:
What that actually means is that, within
cachedQuery
, thethis
value (that you’re not using and don’t need) is bound tomethod
, and thequeryFunction
parameter is set to${thisObject.className}.${method.name}
.If you’re trying to just curry without binding anything, you could do:
However, note that there is no
.className
property on functions or objects, so this would always beundefined
and yourqueryKey
would look like'undefined.find'
. I assume you’re looking for'Blog'
. The closest equivalent would bethisObject.constructor.name
, but I would strongly recommend against using that as a pattern. Unlike class-based languages, the value doesn’t have to be an instance of a class at all, e.g.const Blog = { find: () => null }
, so it would just be'Object'
.Thus, I think it would be clearer to supply the query key and do the currying explicitly: