0

Apologize if the title of the question is misleading. Actually I am looking for the javascript equivalent of the following python code:


## python code
def call_with_context(fn, *args):
  ## code to create context, e.g. profiling, db.connect, or drawing context store stack
  fn(*args)
  ## code to close context 

This implements the similiar functionality as the "with statement" in python, which implements the aspect-oriented paradigm.

So my question is what is the javascript way of doing such things? I have seen some code using Array.prototype.slice(arguments, 1) to do so, but I don't know if this is a common pattern in javascript, or there are better patterns supported in javascript (e.g. by closure) so ppl don't really do that. Pls also correct me if I am using the wrong keywords, because I really dont know how to refer to my problem with a better name than sandwich.

EDT 1: And I appreciate if someone can explain how to return the result of fn(*args) from inside the wrapper call_with_context. thanks!

dolaameng
  • 1,397
  • 2
  • 17
  • 24
  • I think the question might be a little misguided. Although there is some AOP done is Javascript, it's not that common. With first-class functions, and everything dynamic, it's often so easy to overwrite a function with a decorated version that there's no real reason to introduce any substantial AOP tools. – Scott Sauyet Jul 07 '12 at 04:01

5 Answers5

1

It sounds like you want to call a method with specific context.

In js, you would typically do...

function someFunction( fn, context ) {
   fn.call( context );
}

var Button = {
   isClicked: false
};
someFunction(function () {
   // this === Button
   this.isClicked = true;
}, Button );

now the this keyword inside of fn will represent the context passed into the method someFunction. This sort of pattern is done quite often. Especially with the callbacks.

Trevor
  • 11,269
  • 2
  • 33
  • 40
  • Thank, you @Trevor. So even if fn is a method associated another object, like "console.log", the call of fn "fn.call" will still be on the correct "this" object. Is my understanding right? Or it is illegal to pass bound method around in js? – dolaameng Jul 07 '12 at 03:38
  • In my example, if you used console as the context, a property of `isClicked:true` would be added to the console object. – Trevor Jul 07 '12 at 03:39
  • The method you pass in will go in as what is already bound to if you omit passing in context. You can pass in any method you want. – Trevor Jul 07 '12 at 03:42
  • Thanks for the clarification. So what about the return value if I want to return the fn.call result from inside someFunction, like directly via "return fn.call(context);" ? – dolaameng Jul 07 '12 at 03:44
  • That return value is whatever `fn` returns. :) – Trevor Jul 08 '12 at 01:02
1

I think a more typical JS way of doing this might be to decorate the function. So if you wanted to wrap your function in something that logged timing, you might create a function like this (off the top of my head):

var createTimer = function(fn) {
    return function() {
        var start = new Date();
        var result = fn.apply(this, arguments);
        console.log("Took " + (new Date() - start) + " ms.");
        return result;
    }
};

var test = function(a, b, c) {
    return a * b + c;
}

test = createTimer(test);

console.log(test(3, 4, 5));

// Took 0 ms.
// 17

The main point is that you might not call something like this:

runTimerAround(test, 3, 4, 5);

although that could also be done in JS, it is, I believe less common than overwriting the functions directly.

Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
0

Something like this

// javascript code
function call_with_context(fn) {
  // code to create context, e.g. profiling, db.connect, or drawing context store stack
  var r = fn.call(Array.prototype.slice.call( arguments, 1)); // remove first arg - fn 
  //code to close context 
  return r; 
}

so you will be able to do this:

call_with_context(myfun,1,2,3);

that will end up in call

myfun(1,2,3);
Trevor
  • 11,269
  • 2
  • 33
  • 40
c-smile
  • 26,734
  • 7
  • 59
  • 86
  • 1
    Thanks! I guess by "arguments.slice" you mean "Array.prototype.slice.call" as arguments is not really an array in js? And when I tried the code in firefox, it always returns None as the result? – dolaameng Jul 07 '12 at 03:54
0

Here is my solution after some search. Hope it is helpful to others.


function call_with_context(fn) {
  // some beginning code
  console.log('begin');

  r = fn.apply(null, Array.prototype.slice.call(arguments, 1));

  // some ending code
  console.log('end');

  return r;
}
dolaameng
  • 1,397
  • 2
  • 17
  • 24
0

After having carefully read through every post/comment, I think the OP is looking for [javascript] and [method-modification]. And answering right away the OP's question about terminology, altering closed functionality in JavaScript has nothing to do with Aspect-oriented Programming unless an implementation that claims to be AO provides abstraction and code-reuse levels for at least Aspect, Advice and Pointcut.

As it already has been commented by Scott Sauyet, everything else can be done by just (manually) wrapping functionality into one another. Here again, I wouldn't go that far and calling it function-composition. In order to qualify for that, there should be at least some tool-sets for it, as they already exist with various implementations of compose and/or curry methods/patterns.

For what the OP is going to achieve there are a whole bunch of before, after around / wrap solutions, mostly unfortunately mentioning AO(P), and in too many cases not taking care of the context or target which is essential and also has been ask for by the OP.

The example I do provide uses a prototypal implementation of Function.around. Because JavaScript already features a standardized bind, I'm firmly convinced that Function.prototype is the right place as well for some other method-modifiers like before, after, around, afterThrowing and afterFinally.

code base that will support the afterwards following example:

(function (Function) {
  var
    isFunction = function (type) {
      return (
           (typeof type == "function")
        && (typeof type.call == "function")
        && (typeof type.apply == "function")
      );
    },
    getSanitizedTarget = function (target) {
      return ((target != null) && target) || null;
    }
  ;
  Function.prototype.around = function (handler, target) { // [around]
    target  = getSanitizedTarget(target);

    var proceed = this;
    return (isFunction(handler) && isFunction(proceed) && function () {

      return handler.call(target, proceed, handler, arguments);

    }) || proceed;
  };
}(Function));

example code, altering a given closed function by additionally provided behavior before and after it and also providing it's context.

var loggingDelegate = function () { // closed code that can not be changed for any reason.

  this.log.apply(this, arguments);
};


loggingDelegate.call(console, "log", "some", "arguments");


var interceptedLoggingDelegate = loggingDelegate.around(function (proceed, interceptor, args) {

  // everything that needs to be done before proceeding with the intercepted functionality.


  // [this] in this example refers to [console], the second argument of the [around] modifier.

  this.log("proceed:", proceed);         // the original functionality  - here [loggingDelegate].
  this.log("interceptor:", interceptor); // the modifying functionality - [around]s 1st argument.
  this.log("args:", args);               // the arguments that get passed around.


  proceed.apply(this, args);
  // or:
  //return proceed.apply(this, args);
  // or:
  //var result = proceed.apply(this, args);


  // everything that still needs to be done after invoking the intercepted functionality.

  // if necessary:
  //return result;

}, console); // [console] has to be provided as target to the modified [loggingDelegate].


interceptedLoggingDelegate("intercept", "and", "log", "some", "arguments");
Community
  • 1
  • 1
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37