Question Details

No question body available.

Tags

design-patterns javascript singleton globals instance

Answers (4)

December 1, 2025 Score: 4 Rep: 84,688 Quality: Medium Completeness: 60%

You are correct in that lots of languages, even OOP languages use tricks like this. eg HttpContext.Current in net framework.

You need to access a context for the current operation, but rarely, so you don't want to force all the function calls or object constructors to accept a context parameter just in case its needed.

Here is a tricky way to do it in javascript I worked out, similar to your module closure approach.

  class contextState 
  {
    static run = (state, func) => {    
      var mystate = state;
      var f = eval(String(func)) //dynamically adds the function to the closure
      return setTimeout(f, mystate); //call with timeout to demonstrate state is not lost
    }
  }

callback = () => { var x = mystate //still have to work out how to make this a static console.log(x) }

contextState.run(2000, callback) contextState.run(1000, callback)

We can expand on this trick to use the stack trace as state, which we can read back later and use to retrieve the "global state" for the function chain.

class contextState 
  {
    static id = 0;
    static states = new Map();
    static run = (state, func) => {    
      const id = contextState.id++;

const wrapString = `const wrap${id}wrap = () => { func(); }; wrap${id}wrap()`

contextState.states.set(${id}, state); const wrap = () => eval(wrapString) //dynamically names the function so that the id appears in the stacktrace return setTimeout(wrap, state); //call with timeout to demonstrate state is not lost }

static getState = () => { var err = new Error(); var id = contextState.extractId(err.stack); return contextState.states.get(id); }

static extractId = (stack) => { const regex = /wrap(.*?)wrap/g; let matches = []; let match;

while ((match = regex.exec(stack)) !== null) { matches.push(match[1]); } return matches[0]; } }

callback = () => { var x = contextState.getState(); console.log(x) } callback0 = () => { callback() }

contextState.run(2000, callback0) contextState.run(1000, callback0)

This mirrors the syntax of AsyncLocalState and allows user defined functions to pick up the state from the static reference.

However, If i'm weighing in on the OOP vs Functional discussion, we have to address the fact that the problem statement, wanting to include state with a function; is exactly what OOP classes do and what functional avoids.

By using this style of approach you straddle the two concepts and maybe have the worst of both.

November 30, 2025 Score: 2 Rep: 253 Quality: Low Completeness: 60%

As the experts in this community pointed out in the comment, the solution is obvious: use OOP, enclose the state in objects and live a happy life:

// myLibrary/index.ts
import {F} from './f';

export class MyLibrary { constructor(name) { // the state is this! this.instanceName = name; } whatever() { const f = new F(this); f.f(); // no need to pass f any arguments!!! } }

// myLibrary/f.ts
export class F {
  constructor(state) {
    this.state = state;
  }
  f() { // look mom: no arguments!!!
    return this.state.instanceName;
  }
}

Makes me wonder why Node.js would bother with AsyncLocalStorage. Or why languages have thread-local storage. Or why Haskell has implicit arguments. Or why Agda's modules can be parameterized... Didn't they know they can just hide the state in objects?




In reality, this is not a real solution. state.f() and f(state) are the same thing. They are isomorphic:

// isomorphism
function methodToFn(method) {
  return (state, ...args) => method.call(state, ...args);
}
function fnToMethod(fn) {
  return function (...args) {
    return fn.call(null, this, ...args);
  };
}
// testing the isomorphism
const fn1 = (state, y) => state.x + y;
const method1 = function (y) {
    return this.x + y;
};

const fn2 = methodToFn(method1); const method2 = fnToMethod(fn1); const fn3 = methodToFn(fnToMethod(fn1)); const method3 = fnToMethod(methodToFn(method1));

function newObject(method, state) { return Object.assign({ method }, state); }

const state = { x: 2 }; const y = 3;

// all these things should have the same result console.log(fn1(state, y)); console.log(fn2(state, y)); console.log(fn3(state, y)); console.log(newObject(method1, state).method(y)); console.log(newObject(method2, state).method(y)); console.log(newObject(method3, state).method(y));

In either case you can't just use the function/method on its own, but you need to carry around its state together with it. For instance you can't do array.map(f)/array.map(state.f) but you have to do array.map(()=>f(state))/array.map(()=>state.f()).

All the solutions I mentioned (Node.js' AsyncLocalStorage, thread-local storage, Haskell's implicit parameters, Agda's parametrized modules) are meant to offer the ease of use of global variables while fixing their problem. And that's exactly what I'm looking for.

What I'm asking for is "instance-local globals". The state should be accessible via some globals which are local to the instance. No explicit passing of state, neither via argument, nor via this.

December 3, 2025 Score: 1 Rep: 253 Quality: Low Completeness: 80%

Another idea, heavily inspired by the one Ewan proposed, but simpler.

I could have a mutable global variable, and update it with the current instance every time you enter it.

let myLibState; // global
// myLibrary/index.ts
import f from './f';

export class MyLibrary { constructor(name) { myLibState = this; // updating the global ! this.instanceName = name; } whatever() { myLibState = this; // updating the global ! f(); } }

// myLibrary/f.ts
export function f() {
  return myLibState.instanceName;
}

The tricky part, just like in Ewan's answer, is to always update the global every time you enter the library instance.

Entering the library instance could mean:

  • The user calls a function of a library's instance. Like lib1.whatever() in the example code.

  • An function gets called asynchronously. For instance:

    // this wouldn't work:
    // functions called asynchronously might have the wrong myLibState
    // setTimeout(f, 1000);

    // here we are within the right instance // let's back up the myLibState const savedInstancemyLibState = myLibState; setTimeout(()=>{ // to then restore it in the closure: myLibState = savedInstancemyLibState; return f(); }, 1000);

  • The user calls someClosure() that was created and returned within the library instance. For instance:

    function createClosure() {
        // similarly to the above, we can't just return this closure
        // it might get called on a wrong myLibState
        // return ()=>console.log(f());

    // createClosure is called within the right instance // let's back up the myLibState const savedInstancemyLibState = myLibState;

    return ()=>{ // to then restore it in the closure: myLibState = savedInstancemyLibState;

    console.log(f()); }; } }

And if you want to allow one library instance to use another, you'd need to restore the previous global variable every time you exit an instance.

Of course some utility functions could make the process much less annoying:

function instanceRun(callback) {
  const savedInstanceMyLibState = myLibState;
  return ()=>{
    const currentInstanceMyLibState = myLibState;
    myLibState = savedInstanceMyLibState;
    const out = callback();
    myLibState = currentInstanceMyLibState;
    return out;
  };
}
// wrapping f for an async call
setTimeout(instanceRun(f), 1000);

// wrapping the ()=>console.log(f()) closure function createClosure() { return instanceRun(()=>console.log(f())); }

However the issues remain: forgetting to wrap anything anywhere might result bugs. Subtle and very hard to find bugs.

December 10, 2025 Score: 1 Quality: Low Completeness: 70%

JavaScript functions call() and bind() support passing the context object when calling functions/methods. The JavaScript call() & apply() vs bind()? Stack Overflow thread lists details about each function.

Use .bind() when you want that function to later be called with a certain context, useful in events. Use .call() or .apply() when you want to invoke the function immediately, and modify the context.