blog/posts/scope-with-javascript.md

3.5 KiB

title description date tags slug draft
JavaScript Scoping A blog post talking about how JavaScript handles scoping 2024-08-11
development
javascript
typescript
javascript-scoping true

The EventEmitter Class

The other day I was reading through the Types file of the MariaDB JavaScript library (since its the better MySQL library for Node.js), and I noticed that the PoolConnection class actually extends the EventEmitter class from Node.js. That in and of itself isn't that exciting, but it had me thinking about the utility of extending that particular class, and how the scopeing of the code used in that class would actually work.

I never put a lot of thought into it, but if you have used the EventEmitter class in JavaScript at any point in your life, you know that you can attach callbacks to events that will be run when that particular event is emitted.

import EventEmitter from "node:events";

const em = new EventEmitter();
em.on("some-event", function () {
    console.log("Something happened.");
});

em.emit("some-event");

When you call em.emit("some-event") anywhere this EventEmitter exists in code, you will get a console log from inside that function. Okay, makes sense, so what's interesting about this?

Well the scope of the callback function actually ineherits the called method's properties and methods. This can get really interesting if you are extending the functionality of the EventEmitter class - like in the MariaDB module's PoolConnection class.

Getting In-Depth with Scope

So say we had a class that extends the EventEmitter class and had some methods with some state we cared about in the callback. In the MariaDB context that could be anything that has to do with the connection pool - like maybe how many open connections are available. We can sketch this out in a very trivial class example in TypeScript.

import EventEmitter from "node:events";

class SomePool extends EventEmitter {
    private freeConnections: number;

    public constructor() {
        super();
        this.freeConnections = 4;
    }

    public getFreeConnectionCount(): number {
        return this.freeConnections;
    }

    public function getConnection() {
        this.emit("pre-connect");

        if (this.freeConnections) {
            // get a connection
        }
    }
} 

So now, in any callback function* we can get the total number of free connections in the SomePool class.

const pool = new SomePool();
pool.on("pre-connect", function () {
    if (this.freeConnections) {
        this.freeConnection = this.freeConnection - 1;
    }
});

This is a really impractical use of EventEmitter, but I think it does a good job at illustrating the scope of the callback function on the on method and how it can alter the state of the SomePool class. There is definitely a case for when this can be super helpful and useful.

The Arrow Function

One of the big things in JavaScript is to use this nifty little short-hand to create a function:

[1, 2, 3].map((i) => i + 1);

This is super useful to keeping code clean and concise - but there is a huge caveat using the arrow function over the classic function definition. When you do an arrow function, you are not inheriting the scope of the object you are attaching the callback to.

So in the example above, the object is the Array object, implicitly instantiated, and having its method map called (which iterates over each of the items in an Array, and does something, in this case, adding 1 to it).