2013-07-08 34 views
5

Tôi có một biến can_run, có thể là 1 hoặc 0 và sau đó tôi có một hàng đợi các hàm, sẽ chạy ngay khi biến được chuyển từ 0 thành 1 (nhưng chỉ có 1 hàm như vậy tại một thời điểm) .Hàng đợi giống như semaphore trong javascript?

Ngay bây giờ, những gì tôi làm là

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; 

Nó hoạt động, tuy nhiên, nó không tấn công tôi như rất hiệu quả để có khoảng 100 timeout liên tục chạy. Một cái gì đó giống như semaphore sẽ có ích ở đây; nhưng nói chung, các semaphores không thực sự cần thiết trong JavaScript.

Vì vậy, sử dụng ở đây?

chỉnh sửa: Tôi đã viết "hàng đợi các hàm" nhưng như đã thấy ở đây, tôi thực sự không quan tâm đến thứ tự.

+0

Bạn đang sử dụng bất kỳ thư viện nào như jQuery hay Dojo? Chúng có khả năng trì hoãn phù hợp với cách sử dụng của bạn. –

+1

Bạn cần tìm hiểu về các lời hứa. Xem Q, hoặc hoãn lại của jQuery. – djechlin

+0

@karel - vui lòng không chỉnh sửa các câu hỏi như vậy ... – Neal

Trả lời

25

Đây là một lớp Queue đẹp bạn có thể sử dụng mà không việc sử dụng timeout:

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; 

})(); 

Nó có thể được sử dụng như sau:

// 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); 

Fiddle Demo: http://jsfiddle.net/maniator/dUVGX/


Đã cập nhật để sử dụng es6 và es6 mới Hứa hẹn:

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

Nó có thể được sử dụng trong cùng một cách:

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

Mặc dù bây giờ thời gian này nếu các giá trị thông qua là một lời hứa vv hoặc một giá trị, nó được truyền cho hàm tiếp theo tự động.

Fiddle: http://jsfiddle.net/maniator/toefqpsc/

+1

'add_function' nên là' addFunction' hoặc đơn giản là 'add' hoặc' push'. Bạn cũng có thể giới thiệu 'unshift' để thêm các hàm vào đầu hàng đợi. – Shmiddty

+1

@Shmiddty so needy :-P Tôi không thấy bất kỳ vấn đề nào với việc đặt tên cho 'add_function', tất cả phụ thuộc vào quy ước đặt tên của bạn. Và có các phương pháp khác có thể được thêm vào cho các chức năng khác, nhưng chúng không cần thiết cho câu trả lời này. – Neal

+1

Tôi cảm thấy như tên trường hợp không lạc đà trong javascript là phản trực giác, vì chính ngôn ngữ này sử dụng trường hợp lạc đà cho mọi thứ. – Shmiddty

7

Tôi không chắc chắn cách tốt nhất để làm điều này trong đồng bằng JS nhưng nhiều thư viện đã hoãn lại việc triển khai là rất hữu ích đối với trường hợp sử dụng này.

Với 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 

EDIT Một trong những điều tốt đẹp về những thư viện hỗ trợ deferreds được rằng họ thường tương thích với các sự kiện Ajax, và lần lượt đối tượng thu nhập hoãn lại khác, do đó bạn có thể tạo ra chuỗi phức tạp, kích hoạt sự kiện trên Ajax hoàn thành, hoặc kích hoạt gọi lại 'thực hiện' sau khi nhiều điều kiện được đáp ứng. Đây là khóa học có chức năng cao cấp hơn, nhưng thật tuyệt khi có trong túi sau của bạn.

+4

Ngoài ra còn có một lib độc lập cho deferred's: https://github.com/heavylifters/deferred-js – sroes

+0

Tôi chắc chắn sẽ thử điều này. Không có hành vi phạm tội cho giải pháp @Neal, nhưng điều này có vẻ đẹp hơn. –

2

Ngoài các câu trả lời hữu ích khác tại đây, nếu bạn không cần các tính năng bổ sung mà các giải pháp đó cung cấp, thì asynchronous semaphore rất dễ thực hiện.

Đây là khái niệm cấp thấp hơn so với các tùy chọn khác được trình bày ở đây, vì vậy bạn có thể tìm thấy những tiện ích này thuận tiện hơn cho nhu cầu của mình theo số . Tuy nhiên, tôi nghĩ rằng các ẩn dụ không đồng bộ có giá trị biết, ngay cả khi bạn sử dụng các tóm tắt cấp cao hơn trong thực tiễn .

Nó trông giống như sau:

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

Và bạn gọi nó như thế này:

var busy = sem(run_me_asap); 

busy là một chức năng duy trì một truy cập nội bộ của hành động không đồng bộ mà nó đang đợi. Khi đó bộ đếm nội bộ bằng không, nó sẽ kích hoạt chức năng run_me_asap, mà bạn cung cấp.

Bạn có thể tăng bộ đếm nội bộ trước khi chạy một hành động không đồng bộ với busy(1) và thì hành động không đồng bộ có trách nhiệm decrementing truy cập với busy(-1) khi nó hoàn tất. Đây là cách chúng tôi có thể tránh sự cần thiết cho bộ hẹn giờ. (Nếu bạn thích, bạn có thể viết sem để nó trả về một đối tượng với incdec phương pháp thay vào đó, như trong bài viết Wikipedia; này chỉ là cách tôi làm điều đó.)

Và đó là tất cả các bạn phải làm để tạo ra một không đồng bộ semaphore.

Đây là ví dụ về việc sử dụng. Bạn có thể xác định hàm run_me_asap như sau.

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

funcs có thể là danh sách các chức năng mà bạn muốn chạy trong câu hỏi của bạn. (Có lẽ đây không phải là khá những gì bạn muốn, nhưng thấy 'NB' của tôi dưới đây.)

Sau đó ở nơi khác:

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

Khi cả hai hoạt động không đồng bộ đã hoàn thành, busy 's truy cập sẽ được thiết lập về 0 và run_me_asap sẽ là được gọi.

N.B. Cách bạn có thể sử dụng các ẩn dụ không đồng bộ phụ thuộc vào kiến ​​trúc mã của bạn và các yêu cầu của riêng bạn; những gì tôi đã đặt ra có thể không chính xác những gì bạn muốn. Tôi chỉ cần cố gắng cho bạn biết cách hoạt động của chúng; Phần còn lại là tùy thuộc vào bạn!

Và, một lời khuyên: nếu bạn đã sử dụng đồng bộ Cột sau đó tôi muốn khuyên bạn nên che giấu sự sáng tạo của họ và các cuộc gọi đến busy trừu tượng đằng sau cao cấp nên rằng bạn không xả rác ứng dụng của bạn mã với chi tiết cấp thấp.

Các vấn đề liên quan