Javascript 覆盖回调的范围

Javascript overwrite the scope of a callback

本文关键字:范围 回调 覆盖 Javascript      更新时间:2023-09-26

我最近偶然发现了这个问题,经过一段时间的阅读,我找不到特别满足这个用例的答案。

我正在尝试在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

换句话说,我正在寻找一种将回调"包装"在不同的全局范围内的方法。我知道你可以做类似的事情传递环境或使用它;但是这样的方法总是强制回调函数显式引用环境,将回调代码转换为类似

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

我特别在寻找一种解决方案,该解决方案保留了直接从 start 回调中使用全局引用的可能性。

随意使用任何可以以某种方式填充或与节点兼容的 ES6/ES7 功能,这不仅仅是为了生产代码而只是一种有趣的练习。

编辑:我将解释一般问题,因为许多人认为此解决方案可能不是我实际想要的。

我最近了解了STM(https://wiki.haskell.org/Software_transactional_memory(并希望在JS中尝试类似的想法。当然,js 在单个线程上运行,但这个想法是为原子块中运行的不同回调提供相同级别的隔离。

用户具有某种共享事务变量。对此的操作变量必须包装在原子块中。幕后发生的事情是,原子块中的操作不是在实际的TVar上执行的,而是在一些MockTVar上执行的,它只是将所有读写记录在日志中。调用 done 时,将检查日志以查看执行的操作是否与 TVars 的当前状态一致;如果是现在在实际 TVars 上执行的更新并且我们完成了(这称为提交(。如果不是,则丢弃日志并再次运行回调。这是代码的一个小示例

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()
 })

}(

在此示例中,进程 a 将尝试提交,但由于进程 b 更改了 x 的值(并在 a 之前提交了该更改(,提交将失败,回调将再次运行。

如您所见,我在回调中返回了 mockTVars,但我觉得这有点丑陋,原因有两个:1(如果你想锁定多个变量(你通常会这样做(,我别无选择,只能返回一个模拟TVars数组,迫使用户如果他想干净地使用它们,就一个接一个地提取它们。2(如果用户希望能够在不失去理智的情况下推理正在发生的事情,则由用户来确保传递给回调的mockTVar的名称与实际TVar的名称匹配。我的意思是,在这一行中

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

由用户使用相同的名称来引用实际的 TVar 和模拟的 TVar(在本例中名称为 x(。

我希望这个解释是有帮助的。感谢所有花时间帮助我的人

我仍然希望您描述您尝试解决的实际问题,但这里有一个想法,即创建全局对象的副本,将其传递给回调,回调可以使用与全局相同的名称,然后它将"覆盖"对该范围的全局访问。

    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>");
    }