2013-07-08 43 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背後更高層次的抽象性,因此 ,你不亂拋垃圾應用程序的調用代碼與 低級細節。