DEV Community

Understanding Callbacks

Briggs Elsperger on March 15, 2022

Understanding Callbacks Callbacks seem to be a sticking point for people new to programming. Put simply, callbacks are functions that ar...
Collapse
 
peerreynders profile image
peerreynders • Edited

These are just a shorter way to write a function.

I understand this is content for beginners ... but there seems to be this idea that arrow functions were introduced in ES2015 for their fashionable terseness.

The reason for their inclusion is that they are different to traditional function expressions - which is related to their use as callbacks.

MDN: "Arrow functions establish this based on the scope the Arrow function is defined within."

See also: Function Context.

In practical terms this means that when an arrow function is created inside an object method it automatically binds to the this of the object the method was called on, so it retains access to the original creating object even when it's passed as a callback to another function (or method of another object).

Regular functions do not behave this way. Regular functions have to be explicitly bound to the object.

As a trade off arrow functions don't support apply, bind, or call and as a consequence arrow functions cannot be shared via the prototypal inheritance mechanism (given that each arrow function's this is statically bound it no longer can be shared via a dynamically passed this).

function log(text) {
  console.log(`${text}: ${this.someValue}`);
}

const instance = {
  someValue: 'Instance',
  passArrow() {
    const callback = (text) => log.call(this, text);

    setTimeout(callback, 100, 'Arrow');
  },
  passFn() {
    const callback = function (text) {
      log.call(this, text);
    };
    setTimeout(callback, 200, 'Function');
  },
};

globalThis.someValue = 'Global';
instance.passArrow(); // Arrow: Instance
instance.passFn(); // Function: Global
Enter fullscreen mode Exit fullscreen mode
Collapse
 
i3uckwheat profile image
Briggs Elsperger

That's exactly why I added We're going to ignore the this binding rules for these functions for now. in the article, perhaps I should have expanded on that a bit more. This comment here explains it very well though!

Collapse
 
peerreynders profile image
peerreynders

Re-reading that paragraph again:

"Arrow functions differ from regular functions in some nuanced ways. To simplify the discussion we're steering clear of those differences."

Beginners tend to latch onto "just a shorter way to write a function" as "the truth" because it's so (convenient and) easy to remember - using it as a justification to use arrow functions exclusively even when it's inappropriate (e.g. as class methods).

Thread Thread
 
i3uckwheat profile image
Briggs Elsperger

I'll consider making some edits, thank you

Collapse
 
alexerdei73 profile image
AlexErdei73

Thank Mr Elsperger for the great article. The article is really illuminating regarding callbacks. He is well aware that there is a minor difference regarding the context between arrow functions and regular functions. It is perhaps more important than the syntax difference as anybody with a little ES6 JS experience can refactor one form to the other. This comment is great to point out the real difference between the two with this great code sample. Maybe the code sample can be simplified slightly to make it more obvious, what's going on.
The important question is weather the article should contain this difference as detailed as in this comment. In my opinion Mr Elsperger is not far from the truth that real beginners can get confused with details like this. I think he has got experience with these people as he contributes actively to The Odin Project, which is a curriculum for people, who want to become self trained web developers, like myself.
As far as I have seen this is the only real difference between arrow functions and regular function expressions, therefore it would be great to include it in the article in some less confusing form. He could use for example small print and warn beginners that they can skip this part not to get confused. This is common practice in scientific articles.
Why is this fine detail less important? JS is not a language for primarily doing OOP, but functional programming. It means listeners hardly ever use 'this'. It's a concept of OOP, where inheritance is highlighted. Of course people, who want use JS for OOP or people who use React class components (OOP syntax) must be aware of all this.

Collapse
 
peerreynders profile image
peerreynders

JS is not a language for primarily doing OOP, but functional programming.

  1. JavaScript is not a "functional programming language"
  2. In JavaScript class is merely a template for creating objects; it's not a class in the class-based object oriented sense.
  3. this is called the function context; so it can be used to access the object the function was invoked on but it is also used in other ways.

Please read this: Function-Oriented

Thread Thread
 
alexerdei73 profile image
AlexErdei73

You are of course right. I don't have very strong language theoretical knowledge. I am only fluent in two languages.

  1. Object Pascal - I am fluent, but my level is maximum low intermediate. I didn't have a chance to do very sophisticated OOP with this, when I was young, I did basic OOP. It's a strongly typed compiled language, very fast after compilation and had an editor with amazing features that time. By today it's nothing of course. The damned thing is still very fast though.
  2. Java Script - I think I am better with this, thanks to The Odin Project and Mr Elsperger and a lots of other guys in their community. My level might be high intermediate in this. Where I happened to use Java Script in practice on the front-end I did some OOP with it. OOP is possible with a couple of different syntax in JS. Classes are practical, and these are similar to other languages, like Object Pascal for example. Despite of this it's far from ideal to do OOP in it. Some important syntax elements are missing (like excess modifiers) and auto completion, which can be a great practical help, is restricted here, because the weakly typed nature of the language. ES6 classes are here just a different syntax for the Constructor function syntax, which is the way how the language is built itself. So yes the language is built on OOP principles itself, but the inner working is different from typical OOP languages and the OOP tools are restricted. In my opinion JS is not really for OOP programming for the above mentioned reasons. Those, who want to do OOP with it, are better to use TypeScript. TypeScript comes with another syntactic layer on the top of JS, so it's the tool for OOP programming. What we usually do with JS only is not really OOP. JS is much more often used functional programming ways and functions are the most important building blocks here. This is certainly true for programming in modern React, where function components are the building blocks of the UI. They are more natural there than the class components, although the two might be equivalent apart from the syntax. Your code sample for the differences between arrow functions and regular function expressions is a great and actually the only example, which I have seen. This is why I liked your comment. The only other place, where I have seen the same things explained on the right way is an older React tutorial. It used 'this' a lot, because that tutorial only operated with class components. The difference was significant there in some cases and the tutorial pointed it out well. Although you are perfectly right, from a pedagogical point of view, Mr Elsperger's approach might be better for beginners, as the difference hardly ever shows in practical JS. On the other hand for the sake of completeness, he could include your sample and explanation in this article, but only for people, who are not truly beginners. So it needs to be well separated from the main content.
Thread Thread
 
peerreynders profile image
peerreynders • Edited

I think you are mistaking my position that it is important to understand this in reference to functions as some kind endorsement of OOP in JavaScript. Nothing could be further from the truth (as should be evident in this comment).

My contention is that understanding the relevance of this is an important part of building JavaScript competence—completely independent of any OOD/OOP competence. In fact MDN calls it class context in the context of class constructors.

When you look at the documentation under this it's also called the execution context.

this is the execution environment that is instantiated by the JavaScript runtime before the function was invoked. Granted 99.9% of the time this is done to emulate a method call on an object but it has other uses.

Here is a weird one:

(function (b,c) {
    return this + b + c;
}).apply(4, [5, 6]);  

// 15
Enter fullscreen mode Exit fullscreen mode

JavaScript is still first and foremost an imperative language. Please see this comment and this one. For example, JavaScript isn't immutable nor does it support persistent data structures and while recursion is supported, tail call optimization (TCO) isn't standard so there is always the risk of blowing the stack.

The fact that JavaScript can support a functional style is largely due to the fact that it was initially developed as Scheme in the browser.

From a beginner's perspective I think a case could be made that arrow functions should be avoided until such time that they are fully versed and comfortable in using standard function declarations and function expressions given that those are the fundamental building blocks of the language; arrow function expressions are a mere convenience feature.

In fact prior to their introduction in ES2015 there were concerns expressed to the TC39 that developers would over-adopt arrow functions; these concerns seem to be bearing out, as now it isn't unusual for beginners to be misapplying them.

Thread Thread
 
alexerdei73 profile image
AlexErdei73

I think we perfectly understand each other, although we form our opinions on a very different basis. You seem to have a high language theoretical knowledge and the knowledge of the history of more programming languages. I only have some very practical programming knowledge mainly only in two languages. Actually the functional programming paradigm or the elements of it is only known for me by JS and completely new, as it is just missing from Pascal. That's truly imperative language without any functional programming tools apart from functions of course.
It is clear for both of us that JS is based on imperative programming and OOP, so it is not a truly functional programming language. It was clearly developed on OOP principles.
All I tried to say is when we apply JS in practice, especially on beginner level, we mainly use functions and functional programming ways of solving our practical problems, instead of OOP. So we hardly ever get bogged into problems with function context, simply because we just do not use too often the value of 'this' inside our functions.
We also agree that using arrow functions too early is itself can be confusing. In my opinion it is better to rely on constructs, which are similar and exist in other languages too. These are usually more elementary constructs. So for me it is obvious to start with regular functions, because they almost just like functions in good old Pascal. Arrow functions are a new concept compared to the good old Pascal. Where is the point, when we can't avoid using them in JS at least without applying 'noisy', not beginner friendly syntax?
My answer would be that when we are doing OOP or even using React class components we cannot avoid using 'this' inside our functions in some cases. The reason is that we need to reference to the object instances, which contain the function. We expect the value of 'this' to be bound to these object instances, because it is the case in good old Pascal and that's why we use 'this'.
In most cases JS works just like good old Pascal, regarding the value of 'this'. In certain cases unfortunately JS fails to behave like we expect. These are not very common cases, instead some marginal situations, like in your code sample. Your instance is not an instance of a class and the functions inside are not class methods either. In that case JS would behave just like good old Pascal, and everything would be fine. In your sample, on the other hand, the two functions are in a simple object and in cases like this, 'this' is only bound to the object automatically if you use an arrow function. If you use a regular function 'this' is bound to the global object or undefined. This JS behaviour is pretty unnatural and not like the good old Pascal at all. And this is the real reason, why arrow functions are unavoidable in these marginal cases, unless you use some 'noisy' explicit binding. Arrow functions of course are used much more often, just because these are simple and short at least after you got used to them.
I get to the same conclusion again, you are absolutely right, but to overwhelm a beginner with this is not wise. Mr Elsperger could edit his article a bit to explain how arrow functions are different from regular functions with binding the value of 'this', but he should separate it from the main material some way. Beginners will hardly ever meet the problem of function context in practical JS until they start using 'this' in their functions. At that point they are doing OOP in JS or programming with React class components, so they are not truly beginners any more.

Thread Thread
 
peerreynders profile image
peerreynders

We expect the value of 'this' to be bound to these object instances

…unfortunately JS fails to behave like we expect.

This JS behaviour is pretty unnatural …

The issue is that you are falling prey to your own expectations.

JavaScript is most despised because it isn’t some other language. If you are good in some other language and you have to program in an environment that only supports JavaScript, then you are forced to use JavaScript, and that is annoying. Most people in that situation don’t even bother to learn JavaScript first, and then they are surprised when JavaScript turns out to have significant differences from the some other language they would rather be using, and that those differences matter.

[Douglas Crockford, JavaScript - The Good Parts, 2008; p.2]

Do yourself a favour and do not expect JavaScript to behave like any other language. It's its own thing.

Don't let superficial familiarity get the better of you. Familiarity is a trap that is going to cost you considerable time and frustration.

Thread Thread
 
alexerdei73 profile image
AlexErdei73

This is certainly true, that the behavior of 'this' is not the same in JS as it is in the good old Pascal and perhaps other OOP languages. I think too much theory leads us too far. Let me share with you the video tutorial, where it is explained when it's happening with React class components.

The problem arises at 01:01:11 and it is explained pretty well. I also prepared a code sample according to your code and his React example. This is the same problem without React, and the code is more elementary than yours and a bit more practical, but also longer.

Image description

And the html:

Image description

It's not for truly beginners, but it is understandable for those, who finished the OOP parts of The Odin Project. I think it's natural to show it in OOP, because we use 'this' there more often.

Thread Thread
 
peerreynders profile image
peerreynders

Please use code blocks in the future for code. Images of code without a suitable alternate text description are not considered accessible.


The problem arises at 01:01:11 and it is explained pretty well.

I'm well aware of this issue and it identifies the primary use case of why arrow functions were introduced with ES2015 before it became fashionable to use arrow functions exclusively. It doesn't change that arrow functions were introduced as a special case feature, not a replacement for the original function syntax which is how many people use them.

I think it's natural to show it in OOP, because we use 'this' there more often.

Again you are judging this by your previous non-JS experience without fully investigating what this actually is in JavaScript. While that is a natural thing to do, all technology is artificial and therefore is shaped by the context in which it is invented. The circumstances around the development of Object Pascal were very different to the circumstances of the development of JavaScript—and it shows.

Wishing one was more like the other is a natural thing to do but ultimately is only wishful thinking destined to be disappointed.

If I had to guess this was likely added to JavaScript so that functions and objects can be easily composed. Note that I'm saying "objects", not "classes". In JavaScript you can actually "code" with objects; in most OOPLs you cannot—you are relegated to defining classes that can create the objects for you at runtime. Clearly this and the prototype chain inspired by Self made it possible to emulate OOP in JavaScript but at its core this is just the function's execution context (a special, invisible argument) set by the JavaScript runtime.

Under 10.3 Execution Contexts

An execution context contains whatever state is necessary to track the execution progress of its associated code.

That's it.
Nothing about objects or OOP.
In JavaScript this can be a primitive value—it doesn't even have to be an object.

And as we are talking about event handling I'd like to direct you to DOM handleEvent: a cross-platform standard since year 2000 (And the workaround to use it in React; vanilla demo, Preact demo).

By passing an object with a handleEvent() method, the this binding problem inherent to functions as event handlers goes away; React implemented it's own synthetic event system that never allowed for that possibility which is why the workaround is necessary.

The point is that this often gets involved when functions and objects work together; the appearance of this doesn't automatically imply the practice of class-based object-orientation in JavaScript.

Thread Thread
 
alexerdei73 profile image
AlexErdei73 • Edited

Thank you for explaining your opinion with such a detail about arrow functions in JS. I apologize for only giving screenshots of my code sample. It might be worth to play around it a bit. I created a tiny repository, you can clone to try the code, arrow-function-example. Because this example does not contain React, other solutions are possible, as you pointed out, apart from an arrow function or binding the value of 'this'. You can for example add a field, called parentComponent, to the buttonFunction object. You can use that in the constructor to add the value of 'this' at that point to the button. In the listener you can reach that with 'this.parentComponent' and the value will be the instance object. Even in React the situation has been changed by introducing function components and hooks. So the video shows the technology at around 2015, when arrow functions got into the language.
Your valuable and rich opinion helped me to change a couple of misunderstanding or misinterpretation, which I had about this question. You pointed out to me the following things:

  1. My opinion is strongly biased by my previous experience in Object Pascal.

  2. The analogy between JS and Object Pascal (and any other language) can be helpful but comes with limitations, simply because these technologies were developed in different times with different purposes, therefore can be and are different.

  3. Particularly the role of 'this' are different in these two languages. In Object Pascal 'this' means strictly the object instance, which the class generates. In JS the meaning is more general and it just simply means the running environment, which a function is getting executed on. This can be even undefined or the value can be just a number or string, not necessarily an object and can explicitly bound to the function with the bind method, not like in Pascal.

  4. In JS the function and Object are the more basic terms and the code can be fully built up from these not like in Pascal. OOP is not possible without classes in Pascal and we all know that classes just a syntactic convenience in JS. I have already known this but it is refreshing to see it from your point of view too.

  5. Arrow functions got into the language to explicitly bind the value of 'this' without applying the bind method. This can solve some marginal problems, but you are not an advocate to apply them in unjustified cases, just because their syntax seem to be simpler or more fashionable than regular functions. So regular functions are the ones which we should generally apply apart from the marginal cases, what arrow functions actually solve. I agree with this. Even if you check my code sample you can see that the arrow function as a method becomes an instance method, but the regular function is a prototype method. So arrow functions are not recommended as methods on the favor of regular functions generally.

  6. I still think this topic is strictly not for beginners, although Mr Elsperger could have included some more detailed explanation instead of simply avoiding it. This explanation should be based on some simple code sample for interested learners, but also should be separated from the main text. It could be beneficial for interested learners, because it helps us understand better the inner workings of the language.

Thank you again for sharing with me your view about this question.

Thread Thread
 
peerreynders profile image
peerreynders • Edited

So regular functions are the ones which we should generally apply apart from the marginal cases, what arrow functions actually solve.

You are phrasing this as an absolute rule.

My context is "when teaching beginners".

Ultimately a senior developer is going to do whatever they want but they are supposed to understand the difference and when they predominantly use arrow functions because that's "all they need" it will hopefully be in code that won't be reviewed by beginners to learn the ins an outs of JavaScript.

In my experience when beginners are introduced to arrow functions too early they make mistakes like using them in class definitions (think organizing related functions around a frequently used data structure—not necessarily OOP) when the shorthand syntax is more appropriate. They simply don't realize that the arrow functions are (unnecessarily) recreated on every object when a single function on the prototype chain will work for all the objects (n.b. my personal preference is to organize functions within modules).

I went through an "avoid this" phase myself (OOP with Functions, How to decide between classes v. closures in JavaScript) but I think it's impossible to work with JavaScript and avoid open source software entirely. And working with OSS you will invariably have to read other developer's code who use a coding style that you have absolutely no control over; at that point you better know how JavaScript works and that includes this.

The other thing you are missing out on with arrow functions is hoisting. While variable hoisting can be confusing, I find function hoisting incredibly useful. It lets me write a "unit of work", give it a DAMP (descriptive and meaningful phrase) name and then put the function anywhere in the source order where it is out of the way because at that point in time the DAMP name tells me everything I need to know.

Also arrow functions can't be named. These days some JavaScript runtimes will grab the name of the variable they are assigned to and add it to the debugging meta data but there has been a long-standing recommendation to use named function expressions to make it easier to identify what you are looking at in the debugger (example).

we all know that classes just a syntactic convenience in JS.

Again careful with absolutes.

A JavaScript runtime can expose native functionality via a class-like interface which is actually a class in every sense of the word (JS classes are not “just syntactic sugar”). The best example is custom elements which can cause problems when you need to compile down to ES 5.1.

Thread Thread
 
alexerdei73 profile image
AlexErdei73

I am of course well aware the function hoisting and I use it just like you do in every day development. When we compare arrow functions and regular ones, arrow functions can be compared better with regular function expressions. These behave the same regarding the hoisting.
The question of variable hoisting is something new for me as I always declare and even try to initialize variables before I use them. This obviously comes from the root of my Pascal training. I would have never thought of the possibility that it can be done any other way even in JS. I may be very wrong with it though:) This opportunity does not mean I will give up my habit regarding variables in my coding style.
The wide range of knowledge you have regarding very different programming languages and about their history and the theory behind them is amazing and unique. You remind me someone, I had conversation with before, but you might be the exact same person just on a different platform. It would be a great pleasure if you could register on my website and post the talk about simplicity there as well. I liked this talk because it underlines the importance of reasonability in your code quality. As he talks about the growing elephant and your task as drag it when it is really big. For him simplicity means the knowledge in design, which keeps this task reasonable. I really liked this talk.
The link is Fakebook. It is just a personal project with a few friends and strangers on it, but that would be a great thing to welcome you there, if you have the time for it.

Collapse
 
marcomoscatelli profile image
Marco Moscatelli

Great content for beginners!

Collapse
 
konadulord profile image
Konadulord

Head aching but I will grab it

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

Very nicely explained 😀

Collapse
 
brunoj profile image
Bruno

love this

Collapse
 
khaledyd profile image
khaledyd

it's what i needed the most to understand the callbacks and fucntion , becouse of this post i can understand what's funtion and whats not this is amazing , thank you so much

Collapse
 
rakshitambi7a profile image
Rakshit Ambi

Thanks for this article. It's the first time I've read about callbacks without feeling confused or overwhelmed."