JavaScript - override a function with a function that contains async callbacks and still return original value

In JavaScript I want to override a function on an object, but still call the original function and return it's value. So I'd normally do something like this:

var render = res.render;

res.render = function() {
    doSomethingNew();
    return render.apply(this, arguments);
};

However, what if that override contains async callbacks that need to be fired first before calling the original function eg:

var render = res.render;

res.render = function() {
    var self = this;
    var args = arguments;

    var middlewares = getSomeMiddleware();

    return callNextMiddleware(middlewares, function() {
        return render.apply(self, args);
    });
};

function callNextMiddleware(middlewares, callback) {
    var middlewareFunc = middlewares.shift();

    if (middlewareFunc) {
        return middlewareFunc.call(function() {
            return callNextMiddleware(middlewares, callback);
        });
    }
    else {
        return callback();
    }
}

Notice that I'm using a 'return' statement where required. I have one problem though, the 'middlewares' variable is an array of functions, each middleware function looks like this:

function myMiddleware(next) {
    performLongRunningAsyncDataAccess(function() {
        next();
    });
}

Because it doesn't use 'return next()' the return value of the original res.render method is never passed back. I can get this to work if I get all the middleware functions to use 'return next()', but they come from an external source so I have no control over them, I can only guarantee that they will call 'next()'.

A bit of background, this is a Node.js app. The middleware is basically Connect middleware, and I'm trying to override the Express.js res.render method.

Generally it is a bad idea to mix asynchronous functions with return statements. Everything that you want to return, you can pass as arguments to your callback functions. So I still hope I understand your code correctly but I would assume, that you call the render function, which then grabs an array of middleware functions. Then you want to execute all the functions in that array, using the next as an callback to the previous. After all the functions have been executed, the render function should be called again, thus creating kind of an infinite loop. Assuming all of that, lets take a look at some of your return statements:

return middlewareFunc.call(function() {
    return callNextMiddleware(middlewares, callback);
});

The first return in this block is useless since middlewareFunc is asynchronous and will therefore most likely return undefined. The second return statement is also useless, since it returns from the function, that you use as callback. But since your callback is just called by using next();, the return value will never be used.

else {
    return callback();
}

In this block callback is the render function. So lets take a look at this function:

res.render = function() {
    var self = this;
    var args = arguments;

    var middlewares = getSomeMiddleware();

    return callNextMiddleware(middlewares, function() {
        return render.apply(self, args);
    });
};

So all last three return statements are essentially there, because you want to return something from your render function. But to be consistent, you should consider using a callback for that function as well:

res.render = function(callback) {
    var self = this;
    var args = arguments;

    var middlewares = getSomeMiddleware();

    callNextMiddleware(middlewares, function() {
        //this will be called after all the middleware function have been executed
        callback();
        render.apply(self, args);
    });
};

So basically you are getting rid of all the return statements and using pure asynchronous design patterns.

callNextMiddleware should return its recursive call's return value, not middlewareFunc's.

if (middlewareFunc) {
    var result;
    middlewareFunc.call(this, function() {
        result = callNextMiddleware(middlewares, callback);
    });
    return result;
}

Fiddle: http://jsfiddle.net/mWGXs