3

I run into many cases where I find myself needing to do something akin to the following

function main() {
    var x = "Something";

    foo('do stuff', function bar(stuffResponse) {
        var y = x + stuffResponse;
    });
}

I.e. a closure where my callback function needs the variable x I defined previously. This bugs me however as I don't like that my bar function depends a variable outside of itself, but most of all because I much prefer writing my code like this instead...

function main() {
    var x = "Something";

    foo('do stuff', bar);
}

function bar(stuffResponse) {
    var y = x + stuffResponse;
}

But obviously if I do this bar has no idea what x is anymore. Is there a clean way to achieve the latter style whilst retaining the former's functionality?

ed'
  • 1,815
  • 16
  • 30
  • 1
    I see no reason to avoid relying on a closure. But, you can always pass `x` as an argument to `bar()` – haim770 Jul 06 '16 at 08:29
  • This is a simple example where it isn't really a problem, when it gets more complicated and I need more stuff inside my closure it becomes hard to follow. And I can't pass `x` as an argument to `bar()`, its used as a callback function – ed' Jul 06 '16 at 08:31
  • The question is if you really need `x` defined in a scope outside of the closure (your snippet does not demonstrate this need). Can you update your snippet to show something a little closer to what you're trying to achieve? – lc. Jul 06 '16 at 08:32
  • 1
    Not that I can think of. Can't you just define `x` outside of `main()`? – Ben Hillier Jul 06 '16 at 08:32
  • 2
    You *can* pass it even if it's a callback. `var x = 1; function bar(x, stuffResponse) {var y = x + stuffResponse;}.bind(null, x)` – rdiz Jul 06 '16 at 08:35
  • I could @BenHillier but I don't want a lot of globals that don't need to be globals, either. Also if `main()` was used inside a loop the value of the global could change repeatedly before the function that needed its current value gets called – ed' Jul 06 '16 at 08:35
  • @EdwardSammutAlessi You do want this to be a global; you're just trying to convince yourself otherwise. If you want a variable defined somewhere else to be available in a stand-alone function, that is, per definition, a global – Ben Hillier Jul 06 '16 at 08:38
  • What I wanted was what @Dencker suggested. Thanks, I had known about `.bind(this)` but didn't know it could take additional arguments to prepend to the function. If you submit as an answer I'd happily select as my answer – ed' Jul 06 '16 at 08:41
  • So I guess you mean, is there any way to have access to a variable when you don't have access to it? –  Jul 06 '16 at 08:49
  • 2
    Closures are very ideomatic in Javascript! –  Jul 06 '16 at 08:54
  • Your question title refers to "pitfall". What pitfall are you referring to, other than the fact you are not emotionally comfortable with closures? –  Jul 06 '16 at 08:56
  • "Pitfall" was the wrong word to use. I do not have any issue with closures, there are many use cases for them and I use them regularly myself. I just want to achieve the functionality of a closure, without using a closure. – ed' Jul 06 '16 at 09:40

3 Answers3

3

A variable can be available in any of these ways:

  1. Passed as a parameter to a function.
  2. Declared within the function.
  3. Declared globally
  4. Available from an enclosing scope.

You have to decide which of these you want to make x available. There's nothing wrong with 4, at all. 2 doesn't apply here. 3 should be avoided. 1 is a reasonable alternative depending on the situation.

  • I have no problem with closures, but it is sometimes undesirable for me to use them, and I was searching for alternatives. I don't understand what my emotions have to do with looking for better ways to do things. – ed' Jul 06 '16 at 09:19
1

As stated in the comments, what you're trying to achieve can be done by binding the variable to your callback. That'll give you a copy of the value instead of referencing the original variable:

function main() {
    var x = "Something";

    foo('do stuff', function bar(x, stuffResponse) {
        var y = x + stuffResponse;
    }.bind(null, x));
}

You can also bind this instead of null. However, it doesn't really make sense since you're not using this.

rdiz
  • 6,136
  • 1
  • 29
  • 41
  • Thanks, exactly what I needed, a way to neatly pass a variable to a callback, without creating a global or using a closure. – ed' Jul 06 '16 at 09:16
  • It's massively unclear what the objective of doing this is, or why it answers what the OP's question was, which I don't know. –  Jul 06 '16 at 12:05
  • @torazaburo I guess OPs objective is to get rid of the possibility that - in the asynchronous nature of JS - if something alters `x` when defined in the scope outside of the callback before it gets called, it'll yield inconsistent results. So I guess he wanted to use the _current_ value of `x` into the callback, not caring when it's going to be called. – rdiz Jul 06 '16 at 12:16
1

You can use the built-in call function: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call and do something like this:

function main() {
  var x = "so clean";

  function foo(cb) {
    cb.call(null, x);
  }

  foo(bar);
}

function bar(something) {
  console.log(something);
}
Cheng Sieu Ly
  • 1,106
  • 9
  • 7