nazrhom
nazrhom

Reputation: 9

Javascript overwrite the scope of a callback

I stumbled into this problem recently and after a while of reading around I couldn't find an answer that satisfies this use case in particular.

I am trying to achieve the following behaviour in javascript

// Lets assume we have some variable defined in global scope
var a = {val: 0} 

// What I want here is a function that sets a.val = newVal
// and then calls the callback. 
var start = function(newVal, cb) {
  ???
}

// such that
start(1, function() {
  setTimeout(function() {
   console.log(a.val) // 1
  }, 1000)
})

// and
start(2,function () {
  console.log(a.val) // 2
})

// but in the original scope
console.log(a.val) // 0

In other words i am looking for a way to "wrap" a callback in a different global scope. I am aware that you can do something similar passing an environment around or using this; but such methods always force the callback functions to refer to the environment explicitly, turning the callback code into something like

start(2,function () {
  console.log(env.a.val) // 2
})

I am specifically looking for a solution that preserves the possibility to use the global reference directly from within the callback of start.

Feel free to use any ES6/ES7 feature that can somehow be shimmed in or is compatible with node, this is not meant for production code just a fun exercise.

EDIT: I will explain the general problem since many people suggested this solution might not be what I am actually looking for.

I recently learned about STM (https://wiki.haskell.org/Software_transactional_memory) and wanted to play around with a similar idea in js. Of course js runs on a single thread but the idea was to provide the same level of isolation to different callbacks running in atomic blocks.

The user has some kind of shared transactional variable. Operations on this variable must be wrapped in atomically blocks. What happens under the hood is that operations in the atomically block are not performed on the actual TVar but on some MockTVar which simply records all the reads and writes in a log. When you call the done the log is checked to see if the operations performed are consistent with the current state of the TVars; if it is the updates now performed on the actual TVars and we are done (this is called a commit). If it is not the log is discarded and the callback is run again. This is a small example of the code

var x = new TVar(2)

// this is process a
process.nextTick(function() {
  atomically(x, function(x, done) {
    a = x.readTVar()

    setTimeout(function() {
      x.writeTVar(a+1)
      console.log('Process a increased, x = ', x.readTVar())
      done()
    }, 2000)
  })
})

// this is process b
process.nextTick(function() {
  atomically(x, function(x, done) {
    var a = x.readTVar()
    x.writeTVar(a+1)
    console.log('Process b increased, x = ', x.readTVar())
    done()
 })

})

In this example process a will try to commit but since process b changed the value of x (and committed that change before a) the commit will fail and the callback will run once more.

As you can see I am returning the mockTVars in the callback, but i find this a bit ugly for two reasons: 1) If you want to lock more than one variable (and you generally do) i have no choice but to return an array of mockTVars forcing the user to extract them one by one if he wants to use them cleanly. 2) It is up to the user to make sure that the name of the mockTVar which is passed to the callback matches the name of the actual TVar if he wants to be able to reason about whats happening without losing his mind. What I mean is that in this line

atomically(x, function(x, done) {..})

It is up to the user to use the same name to refer both to the actual TVar and the mocked TVar (the name is x in this example).

I hope this explanation is helpful. Thanks to everybody that took the time to help me out

Upvotes: 0

Views: 470

Answers (1)

jfriend00
jfriend00

Reputation: 707238

I still wish you would describe the actual problem you're trying to solve, but here's one idea, that makes a copy of the global object, passes it to the callback and the callback can use the same name as the global and then it will "override" access to the global for that scope only.

    var a = {val: 0, otherVal: "hello"} ;
    
    function start(newVal, cb) {
        var copy = {};
        Object.assign(copy, a);
        copy.val = newVal;
        cb(copy);
    }
    
    log("Before start, a.val = " + a.val);
    start(1, function(a) {
       // locally scoped copy of "a" here that is different than the global "a"
       log("Beginning of start, a.val = " + a.val) // 1
       a.val = 2;
       log("End of start, a.val = " + a.val) // 2
    });

    log("After start, a.val = " + a.val);
    
    function log(x) {
        document.write(x + "<br>");
    }

Upvotes: 1

Related Questions