Related
Background
I'm trying to port the elixir's actor model language primitives into JS. I came up with a solution (in JS) to emulate the receive elixir keyword, using a "receiver" function and a generator.
Here's a simplified implementation and demo to show you the idea.
APIs:
type ActorRef: { send(msg: any): void }
type Receiver = (msg: any) => Receiver
/**
* `spawn` takes a `initializer` and returns an `actorRef`.
* `initializer` is a factory function that should return a `receiver` function.
* `receiver` is called to handle `msg` sent through `actorRef.send(msg)`
*/
function spawn(initializer: () => Receiver): ActorRef
Demo:
function* coroutine(ref) {
let result
while (true) {
const msg = yield result
result = ref.receive(msg)
}
}
function spawn(initializer) {
const ref = {}
const receiver = initializer()
ref.receive = receiver
const gen = coroutine(ref)
gen.next()
function send(msg) {
const ret = gen.next(msg)
const nextReceiver = ret.value
ref.receive = nextReceiver
}
return { send }
}
function loop(state) {
console.log('current state', state)
return function receiver(msg) {
if (msg.type === 'ADD') {
return loop(state + msg.value)
} else {
console.log('unhandled msg', msg)
return loop(state)
}
}
}
function main() {
const actor = spawn(() => loop(42))
actor.send({ type: 'ADD', value: 1 })
actor.send({ type: 'BLAH', value: 1 })
actor.send({ type: 'ADD', value: 1 })
return actor
}
window.actor = main()
Concern
Above model works. However I'm a bit concern about the performance impact of this approach, I'm not clear about the memory impact of all the closure contexts it creates.
function loop(state) {
console.log('current state', state) // <--- `state` in a closure context <─┐ <─────┐
return function receiver(msg) { // ---> `receiver` closure reference ──┘ │
if (msg.type === 'ADD') { │
return loop(state + msg.value) // ---> create another context that link to this one???
} else {
console.log('unhandled msg', msg)
return loop(state)
}
}
}
loop is the "initializer" that returns a "receiver". In order to maintain a internal state, I keep it (state variable) inside the closure context of the "receiver" function.
When receive a message, the current receiver can modifies the internal state, and pass it to loop and recursively create a new receiver to replace current one.
Apparently the new receiver also has a new closure context that keeps the new state. This process seems to me may create a deep chain of linked context objects that prevents GC?
I know that context objects referenced by closure could be linked under some circumstance. And if they're linked, they are obviously not released before the inner-most closure is released. According to this article V8 optimization is very conservative on this regard, the picture doesn't look pretty.
Questions
I'd be very grateful if someone can answer these questions:
Does the loop example creates deeply linked context objects?
What does the lifespan of context object look like in this example?
If current example does not, can this receiver creates receiver mechanism ends up creating deeply linked context objects under other situation?
If "yes" to question 3, can you please show an example to illustrate such situation?
Follow-Up 1
A follow-up question to #TJCrowder.
Closures are lexical, so the nesting of them follows the nesting of the source code.
Well said, that's something obvious but I missed 😅
Just wanna confirm my understanding is correct, with an unnecessarily complicated example (pls bear with me).
These two are logically equivalent:
// global context here
function loop_simple(state) {
return msg => {
return loop_simple(state + msg.value)
}
}
// Notations:
// `c` for context, `s` for state, `r` for receiver.
function loop_trouble(s0) { // c0 : { s0 }
// return r0
return msg => { // c1 : { s1, gibberish } -> c0
const s1 = s0 + msg.value
const gibberish = "foobar"
// return r1
return msg => { // c2 : { s2 } -> c1 -> c0
const s2 = s1 + msg.value
// return r2
return msg => {
console.log(gibberish)
// c3 is not created, since there's no closure
const s3 = s2 + msg.value
return loop_trouble(s3)
}
}
}
}
However the memory impact is totally different.
step into loop_trouble, c0 is created holding s0; returns r0 -> c0.
step into r0, c1 is created, holding s1 and gibberish, returns r1 -> c1.
step into r1, c2 is created, holding s2, returns r2 -> c2
I believe in the above case, when r2 (the inner most arrow function) is used as the "current receiver", it's actually not just r2 -> c2, but r2 -> c2 -> c1 -> c0, all three context objects are kept (Correct me if I'm already wrong here).
Question: which case is true?
All three context objects are kept simply because of the gibberish variable that I deliberately put in there.
Or they're kept even if I remove gibberish. In other word, the dependency of s1 = s0 + msg.value is enough to link c1 -> c0.
Follow-Up 2
So environment record as a "container" is always retained, as of what "content" is included in the container might vary across engines, right?
A very naive unoptimized approach could be blindly include into the "content" all local variables, plus arguments and this, since the spec didn't say anything about optimization.
A smarter approach could be peek into the nest function and check what exactly is needed, then decide what to include into content. This is referred as "promotion" in the article I linked, but that piece of info dates back to 2013 and I'm afraid it might be outdated.
By any chance, do you have more up-to-date information on this topic to share? I'm particularly interested in how V8 implements such strategy, cus my current work heavily relies on electron runtime.
Note: This answer assumes you're using strict mode. Your snippet doesn't. I recommend always using strict mode, by using ECMAScript modules (which are automatically in strict mode) or putting "use strict"; at the top of your code files. (I'd have to think more about arguments.callee.caller and other such monstrosities if you wanted to use loose mode, and I haven't below.)
Does the loop example creates deeply linked context objects?
Not deeply, no. The inner calls to loop don't link the contexts those calls create to the context where the call to them was made. What matters is where the function loop was created, not where it was called from. If I do:
const r1 = loop(1);
const r2 = r1({type: "ADD", value: 2});
That creates two functions, each of which closes over the context in which it was created. That context is the call to loop. That call context links to the context where loop is declared — global context in your snippet. The contexts for the two calls to loop don't link to each other.
What does the lifespan of context object look like in this example?
Each of them is retained as long as the receiver function referring to it is retained (at least in specification terms). When the receiver function no longer has any references, it and the context are both eligible for GC. In my example above, r1 doesn't retain r2, and r2 doesn't retain r1.
If current example does not, can this receiver creates receiver mechanism ends up creating deeply linked context objects under other situation?
It's hard to rule everything out, but I wouldn't think so. Closures are lexical, so the nesting of them follows the nesting of the source code.
If "yes" to question 3, can you please show an example to illustrate such situation?
N/A
Note: In the above I've used "context" the same way you did in the question, but it's probably worth noting that what's retained is the environment record, which is part of the execution context created by a call to a function. The execution context isn't retained by the closure, the environment record is. But the distinction is a very minor one, I mention it only because if you're delving into the spec, you'll see that distinction.
Re your Follow-Up 1:
c3 is not created, since there's no closure
c3 is created, it's just that it isn't retained after the end of the call, because nothing closes over it.
Question: which case is true?
Neither. All three contexts (c0, c1, and c2) are kept (at least in specification terms) regardless of whether there's a gibberish variable or an s0 parameter or s1 variable, etc. A context doesn't have to have parameters or variables or any other bindings in order to exist. Consider:
// ge = global environment record
function f1() {
// Environment record for each call to f1: e1(n) -> ge
return function f2() {
// Environment record for each call to f2: e2(n) -> e1(n) -> ge
return function f3() {
// Environment record for each call to f3: e3(n) -> e2(n) -> e1(n) -> ge
};
};
}
const f = f1()();
Even though e1(n), e2(n), and e3(n) have no parameters or variables, they still exist (and in the above they'll have at least two bindings, one for arguments and one for this, since those aren't arrow functions). In the code above e1(n) and e2(n) are both retained as long as f continues to refer to the f3 function created by f1()().
At least, that's how the specification defines it. In theory those environment records could be optimized away, but that's a detail of the JavaScript engine implementation. V8 did some closure optimization at one stage but backed off most of it because (as I understand it) it cost more in execution time than it made up for in memory reduction. But even when they were optimizing, I think it was the contents of the environment records they optimized (removing unused bindings, that sort of thing), not whether they continued to exist. See below, I found a blog post from 2018 indicating that they do leave them out entirely sometimes.
Re Follow-Up 2:
So environment record as a "container" is always retained...
In specification terms, yes; that isn't necessarily what engines literally do.
...as of what "content" is included in the container might vary across engines, right?
Right, all the spec dictates is behavior, not how you achieve it. From the section on environment records linked above:
Environment Records are purely specification mechanisms and need not correspond to any specific artefact of an ECMAScript implementation.
...but that piece of info dates back to 2013 and I'm afraid it might be outdated.
I think so, yes, not least because V8 has changed engines entirely since then, replacing Full-codegen and Crankshaft with Ignition and TurboFan.
By any chance, do you have more up-to-date information on this topic to share?
Not really, but I did find this V8 blog post from 2018 which says they do "elide" context allocation in some cases. So there is definitely some optimization that goes on.
I came across the following code for a callback function. I understand the code, but I can't wrap my head around it. It just seems so counterintuitive.
function greet(name,callback1){
callback1(name)
}
greet("John Cena", function (somerandomName) {
console.log(somerandomName);
return someRandomName;
}); // Output is John Cena and undefined.
This is what I have understood from the code:
We define a function greet, which takes 2 parameters name and callback1. Then we say that callback1's parameter is name. We don't return anything in the greet function, why?
And when we invoke the greet function we pass the 2nd parameter as an Anonymous function whose parameter is someRandomName. Then we console.log(someRandomName).
I added the return someRandomName, but this return did not work and I got the printed statement followed by undefined
Can someone please explain this in simple words, This just seems so counterintuitive.
So I think it is important to understand that functions themselves can be parameters.
In this example you pass a string as the first parameter and then a function that takes that string as a parameter as the second parameter.
Functions do not always need to return something. Often a function might perform a manipulation on the dom, fetch data, configure something or alter pre existing variables. Of course you can return something if that is what is needed.
Adding return like you did does not do much. In order to actually return the name value you would have to write the original function like this.
function greet(name,callback1){
return callback1(name)
}
Then you could do something like this
var wrestler = greet("John Cena", function (somerandomName) {
console.log(somerandomName);
return somerandomName;
});
console.log(wrestler) // prints John Cena
Its sort of an weird example since it serves no real purpose. Something like this might help you see whats going on.
function greet(name, callback) {
callback(name)
}
greet('John Cena', function(name){
console.log('Hello ' + name) // prints Hello John Cena
})
OR return something and use it to manipulate dom
function greet(name, callback) {
return callback(name)
}
var greeting = greet('John Cena', function(name){
return 'Hello ' + name
})
document.getElementById('message').innerHTML = greeting
// somewhere in HTML...
<h1 id='message'></h1>
Either way at least we are doing something to the first parameter now. And the things that you can do with callbacks are limitless.
Callbacks are a fundamental feature of javascript. They come into play alot when the first part of the function is asynchronous, such as a call to an api or database. In this case the first part would be the call to the database and the callback will not be fired until a value is obtained from the initial call. Recently, this callback pattern is being used less due to Promises, but callbacks are still useful.
So an example of a generic api call from a frontend to a backend. This would typically be done using the Fetch Api or with a library like Request or Axios. Just remember, the first function that is calling an endpoint takes some amount of time to execute and get data. The callback will not fire until that data is returned. Of course there would be a function on the backend that sends either an error or data back to the callback. I dont want to over complicate things, rather just give an idea of what callbacks are often used for.
function getDataFromBackend(endPoint, callback) {
callback(error, data)
}
getDataFromBackend('/api/user', function(error, data) {
if (error) {
// handle error - show user error message in ui, etc
}
// do something with data - such as display welcome message to user, etc
})
I would suggest working with callbacks for practice. I find that when I use Node, or build an app with a frontend and backend I implement callbacks a lot for, since there is a lot of asynchronous communication happening. Hope I answered your questions.
First, you have return someRandomName but your parameter is called somerandomName. Variables are case sensitive; that's why your return value is different from what you want.
Your question is Why don't we return anything in the greet function. The answer is "I have no idea." You could return something. Some functions return things; some functions don't. That has nothing to do with the callback arrangement here.
function greet(name,callback1){
return callback1(name)
}
var finalResult = greet("John Cena", function (someRandomName) {
console.log(someRandomName);
return someRandomName;
});
Now finalResult will be "John Cena".
If it helps at all, anywhere you're using an anonymous function, you could just as easily use a named function. (It's often uglier in practice, but for the sake of understanding the concepts...)
function greet(name,callback1){
return callback1(name)
}
function myGreeterFunction(someRandomName) {
console.log(someRandomName);
return someRandomName;
});
var finalResult = greet("John Cena", myGreeterFunction);
Now it's perhaps easier to see that callback1(name) is the same thing as saying myGreeterFunction(name) in this case.
This is a pretty dated approach to JavaScript and you are totally right that it's counter intuitive. In this particular example there's no advantage of writing this with a callback and probably should be written as a promise,
greet("John Cena")
.then(() => {
return 'Next Action'
})
.then(nextAction => {
console.log(nextAction)
})
And as #mark-meyer pointed out this approach would be required if you had an asynchronous event.
Actually an AWS Lamda function actually has an optional callback defined as it's third argument. exports.myHandler = function(event, context, callback) {}. Which to be brutally honest I think is only there to cover cases where third party libraries aren't promised based.
Whilst passing a callback to a function is probably never the correct approach, there's still cases where passing a function to a function is valuable. Perhaps in a pub/sub system.
const events = {}
function subscribe (event, func) {
if (events[event]) {
events[event].push(func)
} else {
events[event] = [func]
}
}
function publish (event) {
if (events[events]) {
events[event].forEach(func => func())
}
}
And if you are writing with a fp approach link in RamdaJs chaining functions is going to be a huge part of what you write.
Does that clear things up a bit #pi2018?
The general technique is called continuation-passing style – contrast with direct style.
The technique does allow for some interesting programs to emerge. Below, we have two ordinary functions add and mult. We write two programs using cont which allows us to string these functions together, where each step is
the continuation of the one before it.
const cont = x => k =>
cont (k (x))
const add = x => y =>
x + y
const mult = x => y =>
x * y
cont (1) (add (2)) (mult (3)) (console.log) // 9
// 1 ->
// x => 2 + x
// cont (3) ->
// x => 3 * x
// cont (9) ->
// x => console.log (x)
// cont (undefined)
We can sequence as many operations as needed –
const cont = x => k =>
cont (k (x))
const add = x => y =>
x + y
const mult = x => y =>
x * y
cont (2) (mult (2)) (mult (2)) (mult (2)) (mult (2)) (console.log) // 32
// 2 ->
// x => 2 * x
// cont (4) ->
// x => 2 * x
// cont (8) ->
// x => 2 * x
// cont (16) ->
// x => 2 * x
// cont (32) ->
// x => console.log (x)
// cont (undefined)
Continuation-passing style can be used to implement concurrent programs, but JavaScript provides a better native concurrency primitive now, Promise. There's even new async and await syntax to make working with Promises easier.
As wikipedia notes, continuation-passing style is used more by compilers and less by programmers. If you're trying to write concurrent programs, I would highly recommend you use Promises instead. Later versions of Node include util.promisify which allows users to convert a continuation-passing style function to a Promise-returning async function.
var a = 1;
function myFunction() {
++a;
return true;
}
// Alert pops up.
if (myFunction() && a === 2) {
alert("Hello, world!");
}
// Alert does not pop up.
if (a === 3 && myFunction()) {
alert("Hello, universe!");
}
https://jsfiddle.net/3oda22e4/6/
myFunction increments a variable and returns something. If I use a function like that in an if statement that contains the variable which it increments, the condition would be order-dependent.
Is it good or bad practice to do this, and why?
Conditions are order-dependent whether you change the variables used in the condition or not. The two if statements that you used as an example are different and will be different whether you use myFunction() or not. They are equivalent to:
if (myFunction()) {
if (a === 2) {
alert("Hello, world!")
}
}
// Alert does not pop up.
if (a === 3) {
if (myFunction()) {
alert("Hello, universe!")
}
}
In my opinion, the bad practice in your code is not the fact that you change the condition's operands value inside the condition, but the fact that your application state is exposed and manipulated inside a function that does not even accept this state changing variable as a parameter. We usually try to isolate the functions from the code outside their scope and use their return value to affect the rest of the code. Global variables are 90% of the time a bad idea and as your code base gets larger and larger they tend to create problems that are difficult to trace, debug and solve.
It's bad practice, for the following reasons:
The code is far less readable than well-constructed code. This is very important if the code is later examined by a third party.
If myfunction is changed later, the code flow is completely unpredictable, and might require a major documentation update.
Small and simple changes can have drastic effects on the execution of the code.
It looks amateur.
If you have to ask, it's hardly a good practice. Yes, it's a bad practice for exactly the reason you mentioned: changing the order of operands of a logical operation should not affect the outcome, and therefore side effects in conditions should generally be avoided. Especially when they are hidden in a function.
Whether the function is pure (only reads state and does some logic) or whether it mutates state should be obvious from its name. You have several options to fix this code:
put the function call before the if:
function tryChangeA() {
a++;
return true;
}
var ok = tryChangeA();
if (ok && a == 2) … // alternatively: if (a == 2 && ok)
make the mutation explicit inside the if:
function testSomething(val) {
return true;
}
if (testSomething(++a) && a == 2) …
put the logic inside the called function:
function changeAndTest() {
a++;
return a == 2;
}
if (changeAndTest()) …
MyFunction violates a principle called Tell, Don't Ask.
MyFunction changes the state of something, thus making it a command. If MyFunction succeeds or somehow fails to increment a, it shouldn't return true or false. It was given a job and it must either try to succeed or if it finds that job is impossible at the moment, it should throw an exception.
In the predicate of an if statement, MyFunction is used as a query.
Generally speaking, queries should not exhibit side-effects (i.e. not changing things that can be observed). A good query can be treated like a calculation in that for the same inputs, it should produce the same outputs (sometimes described as being "idempotent").
It's also important to know that these are guidelines to help you and others reason about the code. Code that can cause confusion, will. Confusion about code is a hatchery for bugs.
There are good patterns like the Trier-Doer pattern which can be used like your code example, but everyone reading it must understand what's happening though names and structure.
The code presents more then one bad practice actually:
var a = 1;
function myFunction() {
++a; // 1
return true;
}
if (myFunction() && a === 2) { // 2, 3, 4
alert("Hello, world!")
}
if (a === 3 && myFunction()) { // 2, 3, 4
alert("Hello, universe!")
}
Mutates a variable in a different scope. This may or may not be a problem, but usually it is.
Calls a function inside an if statement condition.
This do not cause problems in itself, but it's not really clean.
It's a better practice to assign the result of that function to a variable, possibly with a descriptive name. This will help whoever reads the code to understand what exactly you want to check inside that if statement. By the way, the function always return true.
Uses some magic numbers.
Imagine someone else reading that code, and it is part of a large codebase. What those numbers mean? A better solution would be to replace them with well named constants.
If you want to support more messages, you need to add more conditions.
A better approach would be to make this configurable.
I would rewrite the code as follows:
const ALERT_CONDITIONS = { // 4
WORLD_MENACE: 2,
UNIVERSE_MENACE: 3,
};
const alertsList = [
{
message: 'Hello world',
condition: ALERT_CONDITIONS.WORLD_MENACE,
},
{
message: 'Hello universe',
condition: ALERT_CONDITIONS.UNIVERSE_MENACE,
},
];
class AlertManager {
constructor(config, defaultMessage) {
this.counter = 0; // 1
this.config = config; // 2
this.defaultMessage = defaultMessage;
}
incrementCounter() {
this.counter++;
}
showAlert() {
this.incrementCounter();
let customMessageBroadcasted = false;
this.config.forEach(entry => { //2
if (entry.condition === this.counter) {
console.log(entry.message);
customMessageBroadcasted = true; // 3
}
});
if (!customMessageBroadcasted) {
console.log(this.defaultMessage)
}
}
}
const alertManager = new AlertManager(alertsList, 'Nothing to alert');
alertManager.showAlert();
alertManager.showAlert();
alertManager.showAlert();
alertManager.showAlert();
A class with a precise function, that use its own internal state, instead of a bunch of functions that rely on some variable that could be located anywhere. Whether to use a class or not, it's a matter of choice. It could be done in a different way.
Uses a configuration. That means that would you want to add more messages, you don't have to touch the code at all. For example, imagine that configuration coming from a database.
As you may notice, this mutates a variable in the outer scope of the function, but in this case it does not cause any issue.
Uses constants with a clear name. (well, it could be better, but bear with me given the example).
A function that changes stuff. What is the world coming too? This function must change stuff and return different values each time its called.
Consider the dealCard function for a deck of playing cards. it deals the cards 1-52. Each time it is called it should return a different value.
function dealCard() {
++a;
return cards(a);
}
/* we'll just assume the array cards is shuffled */
/* for the sake of brevity we'll assume the deck is infinite and doesn't loop at 52*/
Is it possible to find the name of an anonymous function?
e.g. trying to find a way to alert either anonyFu or findMe in this code http://jsfiddle.net/L5F5N/1/
function namedFu(){
alert(arguments.callee);
alert(arguments.callee.name);
alert(arguments.callee.caller);
alert(arguments.caller);
alert(arguments.name);
}
var anonyFu = function() {
alert(arguments.callee);
alert(arguments.callee.name);
alert(arguments.callee.caller);
alert(arguments.caller);
alert(arguments.name);
}
var findMe= function(){
namedFu();
anonyFu();
}
findMe();
This is for some internal testing, so it doesn't need to be cross-browser. In fact, I'd be happy even if I had to install a plugin.
You can identify any property of a function from inside it, programmatically, even an unnamed anonymous function, by using arguments.callee. So you can identify the function with this simple trick:
Whenever you're making a function, assign it some property that you can use to identify it later.
For example, always make a property called id:
var fubar = function() {
this.id = "fubar";
//the stuff the function normally does, here
console.log(arguments.callee.id);
}
arguments.callee is the function, itself, so any property of that function can be accessed like id above, even one you assign yourself.
Callee is officially deprecated, but still works in almost all browsers, and there are certain circumstances in which there is still no substitute. You just can't use it in "strict mode".
You can alternatively, of course, name the anonymous function, like:
var fubar = function foobar() {
//the stuff the function normally does, here
console.log(arguments.callee.name);
}
But that's less elegant, obviously, since you can't (in this case) name it fubar in both spots; I had to make the actual name foobar.
If all of your functions have comments describing them, you can even grab that, like this:
var fubar = function() {
/*
fubar is effed up beyond all recognition
this returns some value or other that is described here
*/
//the stuff the function normally does, here
console.log(arguments.callee.toString().substr(0, 128);
}
Note that you can also use argument.callee.caller to access the function that called the current function. This lets you access the name (or properties, like id or the comment in the text) of the function from outside of it.
The reason you would do this is that you want to find out what called the function in question. This is a likely reason for you to be wanting to find this info programmatically, in the first place.
So if one of the fubar() examples above called this following function:
var kludge = function() {
console.log(arguments.callee.caller.id); // return "fubar" with the first version above
console.log(arguments.callee.caller.name); // return "foobar" in the second version above
console.log(arguments.callee.caller.toString().substr(0, 128);
/* that last one would return the first 128 characters in the third example,
which would happen to include the name in the comment.
Obviously, this is to be used only in a desperate case,
as it doesn't give you a concise value you can count on using)
*/
}
Doubt it's possible the way you've got it. For starters, if you added a line
var referenceFu = anonyFu;
which of those names would you expect to be able to log? They're both just references.
However – assuming you have the ability to change the code – this is valid javascript:
var anonyFu = function notActuallyAnonymous() {
console.log(arguments.callee.name);
}
which would log "notActuallyAnonymous". So you could just add names to all the anonymous functions you're interested in checking, without breaking your code.
Not sure that's helpful, but it's all I got.
I will add that if you know in which object that function is then you can add code - to that object or generally to objects prototype - that will get a key name basing on value.
Object.prototype.getKeyByValue = function( value ) {
for( var prop in this ) {
if( this.hasOwnProperty( prop ) ) {
if( this[ prop ] === value )
return prop;
}
}
}
And then you can use
THAT.getKeyByValue(arguments.callee.caller);
Used this approach once for debugging with performance testing involved in project where most of functions are in one object.
Didn't want to name all functions nor double names in code by any other mean, needed to calculate time of each function running - so did this plus pushing times on stack on function start and popping on end.
Why? To add very little code to each function and same for each of them to make measurements and calls list on console. It's temporary ofc.
THAT._TT = [];
THAT._TS = function () {
THAT._TT.push(performance.now());
}
THAT._TE = function () {
var tt = performance.now() - THAT._TT.pop();
var txt = THAT.getKeyByValue(arguments.callee.caller);
console.log('['+tt+'] -> '+txt);
};
THAT.some_function = function (x,y,z) {
THAT._TS();
// ... normal function job
THAT._TE();
}
THAT.some_other_function = function (a,b,c) {
THAT._TS();
// ... normal function job
THAT._TE();
}
Not very useful but maybe it will help someone with similar problem in similar circumstances.
arguments.callee it's deprecated, as MDN states:
You should avoid using arguments.callee() and just give every function
(expression) a name.
In other words:
[1,2,3].forEach(function foo() {
// you can call `foo` here for recursion
})
If what you want is to have a name for an anonymous function assigned to a variable, let's say you're debugging your code and you want to track the name of this function, then you can just name it twice, this is a common pattern:
var foo = function foo() { ... }
Except the evaling case specified in the MDN docs, I can't think of any other case where you'd want to use arguments.callee.
No. By definition, an anonymous function has no name. Yet, if you wanted to ask for function expressions: Yes, you can name them.
And no, it is not possible to get the name of a variable (which references the function) during runtime.
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 1 year ago.
I've never had to use callback functions before, so I may have made a completely stupid mistake. I think I somewhat understand the problem here, but not how to solve it.
My code (a bit simplified) is:
for (var i = 0; i < some_array.length; i++) {
var title = some_array[i];
$.getJSON('some.url/' + title, function(data) {
do_something_with_data(data, i);
}
Now as far as I understand, this anonymous function will only be called if getJSON() has received the data. But by this point, i does not have the value I would require. Or, as far as my observation goes, it has the last value it would have after the loop is done (shouldn't it be out of bounds?).
As a result, if the array had a size of 6, do_something_with_data() would be called five times with the value 5.
Now I thought, just pass i to the anonymous function
function(data, i) { }
but this does not seem to be possible. i is undefined now.
You need to understand what a closure is. In JavaScript, there are certain rules about the scope of each variable.
The scope for variables declared implicitly or with var is the nearest/current function (including "arrow functions"), or if not in a function, then the window or other global object appropriate for the execution context (e.g., in Node, global).
The scope for variables declared with let or const (in ES5 and up) is the nearest statement block { /* not an object, but any place that will take executable statements here */ }.
If any code can access a variable in the current scope or in any parent scope, this creates a closure around that variable, keeping the variable live and keeping any object referred to by the variable instantiated, so that these parent or inner functions or blocks can continue to refer to the variable and access the value.
Because the original variable is still active, if you later change the value of that variable anywhere in the code, then when code with a closure over that variable runs later it will have the updated/changed value, not the value when the function or scope was first created.
Now, before we address making the closure work right, note that declaring the title variable without let or const repeatedly in the loop doesn't work. var variables are hoisted into the nearest function's scope, and variables assigned without var that don't refer to any function scope get implicitly attached to the global scope, which is window in a browser. Before const and let existed, for loops in JavaScript had no scope, therefore variables declared within them are actually declared only once despite seeming to be (re)declared inside the loop. Declaring the variable outside the loop should help clarify for you why your code isn't working as you'd expect.
As is, when the callbacks run, because they have a closure over the same variable i, they are all affected when i increments and they will all use the current value of i when they run (which will as you discovered be incorrect, because the callbacks all run after the loop has completely finished creating them). Asynchronous code (such as the JSON call response) does not and cannot run until all synchronous code finishes executing--so the loop is guaranteed to complete before any callback is ever executed.
To get around this you need a new function to run that has its own scope so that in the callbacks declared inside of the loop, there is a new closure over each different value. You could do that with a separate function, or just use an invoked anonymous function in the callback parameter. Here's an example:
var title, i;
for (i = 0; i < some_array.length; i += 1) {
title = some_array[i];
$.getJSON(
'some.url/' + title,
(function(thisi) {
return function(data) {
do_something_with_data(data, thisi);
// Break the closure over `i` via the parameter `thisi`,
// which will hold the correct value from *invocation* time.
};
}(i)) // calling the function with the current value
);
}
For clarity I'll break it out into a separate function so you can see what's going on:
function createCallback(item) {
return function(data) {
do_something_with_data(data, item);
// This reference to the `item` parameter does create a closure on it.
// However, its scope means that no caller function can change its value.
// Thus, since we don't change `item` anywhere inside `createCallback`, it
// will have the value as it was at the time the createCallback function
// was invoked.
};
}
var title, i, l = some_array.length;
for (i = 0; i < l; i += 1) {
title = some_array[i];
$.getJSON('some.url/' + title, createCallback(i));
// Note how this parameter is not a *reference* to the createCallback function,
// but the *value that invoking createCallback() returns*, which is a function taking one `data` parameter.
}
Note: since your array apparently only has titles in it, you could consider using the title variable instead of i which requires you to go back to some_array. But either way works, you know what you want.
One potentially useful way to think about this that the callback-creating function (either the anonymous one or the createCallback one) in essence converts the value of the i variable into separate thisi variables, via each time introducing a new function with its own scope. Perhaps it could be said that "parameters break values out of closures".
Just be careful: this technique will not work on objects without copying them, since objects are reference types. Merely passing them as parameters will not yield something that cannot be changed after the fact. You can duplicate a street address all you like, but this doesn't create a new house. You must build a new house if you want an address that leads to something different.
You could create a closure using an immediate function (one that executes right away) that returns another function:
for (var i = 0; i < some_array.length; i++) {
var title = some_array[i];
$.getJSON('some.url/' + title, (function() {
var ii = i;
return function(data) {
do_something_with_data(data, ii);
};
})());
}
If you can modify the service at some.url, it would be much better if rather than making a separate HTTP request for each item in some_array, you simply passed every item in the array in a single HTTP request.
$.getJSON('some.url', { items: some_array }, callback);
Your array will be JSON serialized and POSTed to the server. Assuming some_array is an array of strings, the request will look like this:
POST some.url HTTP/1.1
...
{'items':['a','b','c', ... ]}
Your server script should then deserialize the JSON request from the request body and loop over each item in the items array, returning a JSON-serialized array of responses.
HTTP/1.1 200 OK
...
{'items':[{id:0, ... }, {id:1, ... }, ... ]}
(Or whatever data it is you're returning.) If your response items are in the same order as the request items, it is easy to piece things back together. In your success callback, simply match the item index with some_array's index. Putting it all together:
$.getJSON('some.url', { items: some_array }, function(data) {
for (var i = 0; i < data.items.length; i++) {
do_something_with_data(data.items[i], i);
}
});
By 'batching up' your requests into a single HTTP request like this, you'll significantly improve performance. Consider that if each network round-trip takes at least 200ms, with 5 items, you're looking at a minimum 1 second delay. By requesting them all at once, network delay stays a constant 200ms. (Obviously with larger requests, server script execution and network transfer times will come in to play, but performance will still be an order of a magnitude better than if you issue a separate HTTP request for each item.)
Create N closures and pass in the value of 'i' each time, like so:
var i, title;
for (i = 0; i < some_array.length; i++) {
title = some_array[i];
$.getJSON('some.url/' + title, (function(i_copy) {
return function(data) {
do_something_with_data(data, i_copy);
};
})(i));
}
I think some browsers have trouble with making multiple asynchronous calls at the same time, so you could make them one at a time:
var i;
function DoOne(data)
{
if (i >= 0)
do_something_with_data(data, i);
if (++i >= some_array.length)
return;
var title = some_array[i];
$.getJSON('some.url/' + title, DoOne);
}
// to start the chain:
i = -1;
DoOne(null);
I had exactly the same issue as the OP but solved it a different way. I replaced my JavaScript 'for' loop with a jQuery $.each which for each iteration calls a function which I think gets over the callback 'timing' issue. And I combined my external data arrays into a JavaScript object so that I could reference both the parameter I was passing on the JSON URL and the other field in the same element of that object. My object elements came out of a mySQL database table using PHP.
var persons = [
{ Location: 'MK6', Bio: 'System administrator' },
{ Location: 'LU4', Bio: 'Project officer' },
{ Location: 'B37', Bio: 'Renewable energy hardware installer' },
{ Location: 'S23', Bio: 'Associate lecturer and first hardware triallist' },
{ Location: 'EH12', Bio: 'Associate lecturer with a solar PV installation' }
];
function initMap() {
var map = new google.maps.Map(document.getElementById('map_canvas'), {
center: startLatLon,
minZoom: 5,
maxZoom: 11,
zoom: 5
});
$.each(persons, function(x, person) {
$.getJSON('http://maps.googleapis.com/maps/api/geocode/json?address=' + person.Location, null, function (data) {
var p = data.results[0].geometry.location;
var latlng = new google.maps.LatLng(p.lat, p.lng);
var image = 'images/solarenergy.png';
var marker = new google.maps.Marker({
position: latlng,
map: map,
icon: image,
title: person.Bio
});
google.maps.event.addListener(marker, "click", function (e) {
document.getElementById('info').value = person.Bio;
});
});
});
}