2013-07-08 44 views
5

我有一个变量can_run,可以是1或0,然后我有一个函数队列,只要变量从0切换到1(但一次只能有一个这样的函数)就应该运行, 。JavaScript中的信号量队列?

现在,我要做的就是

var can_run=1; 
function wait_until_can_run(callback) { 
    if (can_run==1) { 
     callback(); 
    } else { 
     window.setTimeout(function(){wait_until_can_run(callback)},100); 
    } 
} 

//...somewhere else... 

wait_until_can_run(function(){ 
    can_run=0; 
    //start running something 
}); 

//..somewhere else, as a reaction to the task finishing.. 
can_run=1; 

它的工作原理,但是,它不打我作为非常有效的有大约100超时持续运行。像信号量这样的东西在这里很方便。但总的来说,JavaScript中并不需要信号量。

那么,这里使用什么?

编辑:我写了“功能队列”,但在这里看到,我并不真正关心订单。

+0

您是否使用jQuery或Dojo之类的任何库?它们具有与您的使用相匹配的延迟功能。 –

+1

您需要了解承诺。查看Q或jQuery的延期。 – djechlin

+0

@karel - 请不要编辑这样的问题... – Neal

回答

25

这里是一个很好的队列类,你可以使用使用超时:

var Queue = (function() { 

    Queue.prototype.autorun = true; 
    Queue.prototype.running = false; 
    Queue.prototype.queue = []; 

    function Queue(autorun) { 
     if (typeof autorun !== "undefined") { 
      this.autorun = autorun; 
     } 
     this.queue = []; //initialize the queue 
    }; 

    Queue.prototype.add = function (callback) { 
     var _this = this; 
     //add callback to the queue 
     this.queue.push(function() { 
      var finished = callback(); 
      if (typeof finished === "undefined" || finished) { 
       // if callback returns `false`, then you have to 
       // call `next` somewhere in the callback 
       _this.dequeue(); 
      } 
     }); 

     if (this.autorun && !this.running) { 
      // if nothing is running, then start the engines! 
      this.dequeue(); 
     } 

     return this; // for chaining fun! 
    }; 

    Queue.prototype.dequeue = function() { 
     this.running = false; 
     //get the first element off the queue 
     var shift = this.queue.shift(); 
     if (shift) { 
      this.running = true; 
      shift(); 
     } 
     return shift; 
    }; 

    Queue.prototype.next = Queue.prototype.dequeue; 

    return Queue; 

})(); 

,它可以像这样使用:

// passing false into the constructor makes it so 
// the queue does not start till we tell it to 
var q = new Queue(false).add(function() { 
    //start running something 
}).add(function() { 
    //start running something 2 
}).add(function() { 
    //start running something 3 
}); 

setTimeout(function() { 
    // start the queue 
    q.next(); 
}, 2000); 

小提琴演示:http://jsfiddle.net/maniator/dUVGX/


更新为使用es6和新es6承诺:

class Queue { 
    constructor(autorun = true, queue = []) { 
    this.running = false; 
    this.autorun = autorun; 
    this.queue = queue; 
    this.previousValue = undefined; 
    } 

    add(cb) { 
    this.queue.push((value) => { 
     const finished = new Promise((resolve, reject) => { 
     const callbackResponse = cb(value); 

     if (callbackResponse !== false) { 
      resolve(callbackResponse); 
     } else { 
      reject(callbackResponse); 
     } 
     }); 

     finished.then(this.dequeue.bind(this), (() => {})); 
    }); 

    if (this.autorun && !this.running) { 
     this.dequeue(); 
    } 

    return this; 
    } 

    dequeue(value) { 
    this.running = this.queue.shift(); 

    if (this.running) { 
     this.running(value); 
    } 

    return this.running; 
    } 

    get next() { 
    return this.dequeue; 
    } 
} 

它可以以同样的方式被使用:

const q = new Queue(false).add(() => { 
    console.log('this is a test'); 

    return {'banana': 42}; 
}).add((obj) => { 
    console.log('test 2', obj); 

    return obj.banana; 
}).add((number) => { 
    console.log('THIS IS A NUMBER', number) 
}); 

// start the sequence 
setTimeout(() => q.next(), 2000); 

虽然现在这个时候,如果传递的值是一个承诺等或数值,它就会自动传递到下一个功能。

小提琴:http://jsfiddle.net/maniator/toefqpsc/

+1

add_function应该是addFunction或简单的add或push。您还可以引入'unshift'来将函数添加到队列的开头。 – Shmiddty

+1

@Shmiddty so needy :-P我没有看到命名fn add_function的问题,这一切都取决于你的命名约定。其他方法可以添加其他功能,但它们对于此答案不是必需的。 – Neal

+1

我觉得在JavaScript中非骆驼案例的名称是反直觉的,因为语言本身使用骆驼案件的一切。 – Shmiddty

7

我不知道以纯JS做到这一点的最好办法,但许多图书馆都递延实现这对于这种使用情况是非常有用的。

使用jQuery:

var dfd = $.Deferred(); 
var callback = function() { 
    // do stuff 
}; 
dfd.done(callback); // when the deferred is resolved, invoke the callback, you can chain many callbacks here if needed 
dfd.resolve(); // this will invoke your callback when you're ready 

编辑一个关于这些库支持deferreds好东西是它们通常与阿贾克斯的事件兼容,反过来其他递延对象,所以你可以创建复杂的链条,在Ajax上触发事件完成,或者在满足多个条件后触发'完成'回调。这当然是更先进的功能,但在后面的口袋里很好。

+4

还有一个延期的独立库:https://github.com/heavylifters/deferred-js – sroes

+0

我一定会试试这个。 @Neal解决方案没有冒犯,但这看起来更好。 –

2

除了这里的其他有用的答案,如果你不需要这些解决方案提供的额外功能,那么 asynchronous semaphore很容易实现。

虽然这是一个比其他选项 更低级别的概念,但您可能会发现这些方法对于您的需要更加方便 。尽管如此,我认为即使在 练习中使用更高级别的抽象,异步信号也值得知道 。

它看起来是这样的:

var sem = function(f){ 
    var busy = 0; 
    return function(amount){ 
     busy += amount; 
     if(busy === 0){ 
      f(); 
     } 
    }; 
}; 

你调用它是这样的:

var busy = sem(run_me_asap); 

busy是维护它正在等待的 的异步操作的内部计数器功能。当内部计数器达到零时,它会触发您提供的功能 run_me_asap

您可以运行与busy(1)的 异步动作之前递增内部计数器,那么 异步行为负责与busy(-1)递减计数器 一旦它完成。这就是我们如何避免定时器的需求。 (如果你愿意,你可以 写sem,使其返回与incdec方法的对象,而不是像维基百科的文章中,这 就是我如何做到这一点。)

而这一切,你必须如何创建一个异步的 信号量。

下面是它正在使用的一个例子。您可以按如下方式定义功能 run_me_asap

var funcs = [func1, func2, func3 /*, ...*/]; 
var run_me_asap = function(){ 
    funcs.forEach(function(func){ 
     func(); 
    }); 
}); 

funcs可能是你想 运行在你的问题的函数列表。 (也许这是不太你想要什么, 但看到我的“NB”下面。)

然后在别处:

var wait_until_ive_finished = function(){ 
    busy(1); 
    do_something_asynchronously_then_run_callback(function(){ 
     /* ... */ 
     busy(-1); 
    }); 
    busy(1); 
    do_something_else_asynchronously(function(){ 
     /* ... */ 
     busy(-1); 
    }); 
}; 

当异步操作完成,busy的 计数器将会被设置为零,而run_me_asap将调用 。

N.B.如何可能使用异步信号量取决于您的代码架构和您自己的要求 ; 我所列出的可能并不完全是你想要的。我只是 试图告诉你他们是如何工作的;其余的由你决定!

而且,建议的一个字:如果你使用异步 信号灯,那么我建议你隐藏自己的创作 和busy背后更高层次的抽象性,因此 ,你不乱抛垃圾应用程序的调用代码与 低级细节。