在javascript中识别并行异步调用堆栈

在javascript中识别并行异步调用堆栈,javascript,race-condition,callstack,Javascript,Race Condition,Callstack,我想知道是否有一种方法可以让我在javascript中的每个堆栈中使用一种私有领域或私有内存,以帮助我清除竞速情况,特别是在并行setTimeout调用的情况下 例如,假设我有: function foo() { /* some statements */ bar(); } function bar() { throw new Exception("oooh damn!"); } setTimeout(function() { foo(); }, 10); setTi

我想知道是否有一种方法可以让我在javascript中的每个堆栈中使用一种私有领域或私有内存,以帮助我清除竞速情况,特别是在并行setTimeout调用的情况下

例如,假设我有:

function foo() { 
    /* some statements */ 
    bar(); 
} 

function bar() { throw new Exception("oooh damn!"); }

setTimeout(function() { foo(); }, 10);
setTimeout(function() { foo(); }, 10);
我将引发2个异常,但我不知道它对应于哪个调用


我可以实现一种私有领域的东西,但它确实会使代码复杂化,如果有的话,我宁愿使用本机解决方案。

您可以将一些标识符传递到您的foo中以保持跟踪。试试这个:

setTimeout(function () { foo(1); }, 10);
setTimeout(function () { foo(2); }, 10);
并修改foo函数以接受id参数并传递它

function foo(id) {
    /* some statements */
    bar(id);
}

function bar(id) {
    try {
        throw {
            name: "Exception",
            message: "oooh damn!" + id
        }
    } catch (e) {
        console.error(e.name, e.message);
    }
}
参见fiddle中的示例:

因此,如果我这样做:

setTimeout(function () { foo(1); }, 10);
setTimeout(function () { foo(2); }, 10);
然后它回来了:

Exception oooh damn!1
Exception oooh damn!2
Exception oooh damn!2
Exception oooh damn!1
Exception oooh damn!2
Exception oooh damn!1
或者如果我这样做了:

setTimeout(function () { foo(1); }, 10);
setTimeout(function () { foo(2); }, 9);
然后它回来了:

Exception oooh damn!1
Exception oooh damn!2
Exception oooh damn!2
Exception oooh damn!1
Exception oooh damn!2
Exception oooh damn!1
编辑2不必将id作为参数传递:

var currentId = null;
function foo() {
    var id = currentId;        // since it's copied over to 'id', we don't care about 'currentId' anymore
    var bar = function() {
        try {
            throw {
                name: "Exception",
                message: "oooh damn!" + id
            }
        } catch (e) {
            console.error(e.name, e.message);
        }
    }
    /* some statements */
    bar();
}

setTimeout(function () {
    currentId = 1;
    foo();
}, 10);
setTimeout(function () {
    currentId = 2;
    foo();
}, 10);
因此currentId是一个共享变量,但它是在setTimeout结束时设置的,并执行该函数

这样做:

setTimeout(function () {
    currentId = 1;
    foo();
}, 10);
setTimeout(function () {
    currentId = 2;
    foo();
}, 9);
然后它回来了:

Exception oooh damn!1
Exception oooh damn!2
Exception oooh damn!2
Exception oooh damn!1
Exception oooh damn!2
Exception oooh damn!1

我可能会以稍微不同的方式使用闭包,并避免使用全局变量。您可以用一些上下文对象替换ID,该对象允许您计算调用数或处理竞争条件

var invokeWithId = function(id, f){
    return function(){               
        f.apply({id:id}, arguments);
    }
}
setTimeout(invokeWithId(1, foo), 10);

可以使用错误对象的堆栈属性

我更新了甜淀粉酶的小提琴:

两个记录的堆栈跟踪的最后一行将显示哪个setTimeout启动了哪个错误:

Error: 1
    at bar (http://fiddle.jshell.net/robatwilliams/Am8mf/3/show/:28:15)
    at foo (http://fiddle.jshell.net/robatwilliams/Am8mf/3/show/:23:5)
    at http://fiddle.jshell.net/robatwilliams/Am8mf/3/show/:35:26
Error: 2
    at bar (http://fiddle.jshell.net/robatwilliams/Am8mf/3/show/:28:15)
    at foo (http://fiddle.jshell.net/robatwilliams/Am8mf/3/show/:23:5)
    at http://fiddle.jshell.net/robatwilliams/Am8mf/3/show/:36:26 

根据您的上下文,许多浏览器中的典型错误对象将具有附加详细信息以及堆栈属性

例如,V8 Chrome/NodeJS错误以及IE都有一个堆栈属性,可以为您的环境提供更多上下文

try { 
  throw new Error();
} catch(err) { 
  console.dir(err.stack.toString().split('\n'));
}
输出铬:

0: "Error"
1: "    at :3:9"
2: "    at Object.InjectedScript._evaluateOn (:532:39)"
3: "    at Object.InjectedScript._evaluateAndWrap (:491:52)"
4: "    at Object.InjectedScript.evaluate (:410:21)"
输出IE10:

 0 : "Error",
    1 : "   at eval code (eval code:2:3)",
    2 : "   at Global code (Unknown script code:5:1)"
输出Firefox:


    0 : "@chrome://firebug/conte...mmandLineExposed.js:192",
    1 : ""
在一个适当的JS文件/模块中,您将获得相关的文件和行/列

不同的浏览器将有自己的实现细节,但这应该给你你要寻找的上下文

您可能还希望在函数中添加一个名称

setTimeout(function IPityTheFoo() { foo(); }, 10);
setTimeout(function DoingMyFooThing1() { foo(); }, 10);
您可以修改setTimeout函数并汇总所有答案的经验

var oldTimeout = window.setTimeout;
window.setTimeout = function(callback, delay){
    if(callback.name == 'Trace'){
        oldTimeout.apply(this,[function(){
            try{
                callback.apply(this,arguments);
            }
            catch(e){                
                e.message += ' ('+e.stack.split('\n').map(function(e){return '['+e.replace(location.href,'plain_script').replace('@',' in ')+']'}).join(' < ')+')';
                throw e;
            }
        }].concat(Array.prototype.slice.call(arguments,1,arguments.length)));
    }
    else{oldTimeout.apply(this,arguments)};
}

在不使用try/catch、stacks或修改现有代码的情况下,您所能做的就是使用更智能的setTimeout:

(function(){
  if(!setTimeout.id){
  var old=setTimeout, hits=0;
  setTimeout=function timeout(code, delay, id){ 
     var count=hits++;
     setTimeout.calls=setTimeout.calls||{};
     setTimeout.calls[id||count]=code;
     return old(function(){ 
           setTimeout.id=id||count; 
           code.call? code() : Function(code)(); 
     }, delay);  
  };
  setTimeout.id=true;
 }//end setTimeout patcher

}());


function foo() {     /* some statements */ 
    bar(); 
} 


function bar() { throw new Error("oooh damn! #"+setTimeout.id+"\t"+setTimeout.calls[setTimeout.id] ); }

setTimeout(function() { foo(); }, 20, "more");
setTimeout(function() { foo(); }, 10, "something");
setTimeout(function() { foo(); }, 20);
setTimeout(function() { foo(); }, 10);
基本上,这使得setTimeout.id属性可用。因为JS是单线程的,所以在函数完成之前不可能重写此属性,所以最后一个set setTimeout.id是超时函数运行时的当前属性


您可以为此传递第三个参数“忘记浏览器怪癖”(forget browser quirk currying),以手动指定ID,或为其指定索引。这意味着每个调用都有一个唯一的标识符,这可能就是您需要调试的全部内容。如果您需要代码本身,my setTimeout会提供一个.calls属性,这是一个查找,可以让您查看在该ID下计算的代码/函数。

为什么第一个setTimeout代码不先执行?它确实先执行,但是它的执行可能会延迟,所以第二个可能会首先引发异常。你是说,如果foo函数内部有异步的东西?否则,它不应该比第二个延迟。因为它们引用相同的函数foo,所以执行的代码是相同的,所以没有理由使用第一个foo;应该在第二个foo之后执行;再说一遍,除非里面有异步的东西。现在,如果第一个setTimeout名为asdf,第二个setTimeout名为foo,则情况会有所不同。我不确定你是说得很具体还是很宽泛。只是想更好地理解这个问题:是的,/*一些语句*/做的是相关的事情。在这两个调用中可能根本不一样。整个过程都是在一个糟糕的异步上下文中进行的。是的,这就是想法,修复它的调用堆栈id。但我不想这么简单,foo和bar都是例子,它可以是任何调用的函数,我不想在每个函数中添加参数them@Sebas刚才添加了一个示例,您可以在两个foo调用之间共享currentId变量,但这也意味着currentId需要在同一范围内。因为currentId是全局的,如果第一个调用在第二个调用完成后崩溃,我将使用currentId=2而不是1,这是错误的。这就是问题所在。@Sebas我明白你的意思,所以foo应该保留id的引用,而不是使用全局。。。var id=当前id;在foo内部,当bar抛出异常时使用id。可以是barid,也可以是just bar,但它需要对id变量具有可见性。是的,这就像一个全局闭包。我可以照我说的做,但是。。真的,我希望javascript有办法在本地实现这一点。你的答案是正确的,但是我应该提到对foo的调用也可能来自同一行示例:你点击两次按钮,里面每次都有一个ajax调用。问题不清楚,谢谢你的意见。谢谢你的意见,请参阅我对rwm的评论
answer@Sebas,至于输入顺序,您可以为每个函数附加一个id,以便。。。随每个附件递增。命名函数将允许您在函数引用上使用.toString来访问名称。。尽管如此。祝你好运,有很多方法可以剥这只猫的皮。有趣的方法:你可以从论点的角度来识别。谢谢你的投入,这也是一种有趣的方法。从它们的调用序列中识别它们,即使您所建议的不是从本机javascript引擎自动执行的方式,而且由于显然不存在这种可能性,您的答案与我所需要的最接近。我会传递一个时间戳作为参数,这样我就知道哪一个是首先调用的。@Sebas值得注意的是,JS中的时间没有那么细粒度,如果您快速地将两个项目排队,那么两个项目都有可能具有相同的时间戳。。。您可能希望为每个请求使用一个id和一个++my\u id\u变量。