skip to Main Content

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


  1. Chosen as BEST ANSWER

    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

    function makeCachable(method, thisObject) {
        queryKey = `${thisObject.className}.${method.name}`;
        return function(queryParams=null, cacheConfig=DEFAULT_CACHING) {
            return cachedQuery(method, queryKey, queryParams, cacheConfig);
        }
    }
    

    I don't have an explanation for why return cachedQuery.bind(method, queryKey); doesn't work, but the closure does work.


  2. .bind accepts one or more arguments:

    • The first will be used as the this value within the function
    • The remaining arguments are curried, being used in order as the parameters of the function

    Where you are doing:

    cachedQuery.bind(method, `${thisObject.className}.${method.name}`)
    

    What that actually means is that, within cachedQuery, the this value (that you’re not using and don’t need) is bound to method, and the queryFunction parameter is set to ${thisObject.className}.${method.name}.

    If you’re trying to just curry without binding anything, you could do:

    cachedQuery.bind(null, method, `${thisObject.className}.${method.name}`)
    

    However, note that there is no .className property on functions or objects, so this would always be undefined and your queryKey would look like 'undefined.find'. I assume you’re looking for 'Blog'. The closest equivalent would be thisObject.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:

    Blog.find = caching.makeCacheable('Blog.find', Blog.find.bind(Blog))
    
    // small nitpick, the word is cacheable, not cachable
    function makeCacheable(queryKey, queryFunction) {
        return (queryParams = null, cacheConfig = DEFAULT_CACHING) => {
            return cachedQuery(queryFunction, queryKey, queryParams, cacheConfig)
        }
    }
    
    // no longer needs the default values as they're done in the curried function
    // otherwise the same
    async function cachedQuery(queryFunction, queryKey, queryParams, cacheConfig) {
        // ...
    }
    
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search