Семафороподобная очередь в javascript?

У меня есть переменная can_run, это может быть или 1 или 0, и тогда у меня есть очередь функций, которые должны быть запущены, как только переменная переключается из 0 в 1 (но только 1 такая функция одновременно).

Сейчас я занимаюсь

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.

Итак, что здесь использовать?

редактировать: я написал "очередь функций", но, как видно здесь, меня не волнует порядок.

4 ответа

Вот хороший класс Queue, который вы можете использовать без использования тайм-аутов:

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/

Я не уверен, что лучший способ сделать это в простом 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

РЕДАКТИРОВАТЬ Одной из приятных особенностей этих отложенных библиотек является то, что они обычно совместимы с событиями Ajax и, в свою очередь, с другими отложенными объектами, так что вы можете создавать сложные цепочки, инициировать события при завершении Ajax или запускать обратный вызов "done" после нескольких условий. которые встретились. Это, конечно, более продвинутая функциональность, но приятно иметь в своем заднем кармане.

В дополнение к другим полезным ответам здесь, если вам не нужны дополнения, которые предоставляют эти решения, то асинхронный семафор легко реализовать.

Это концепция более низкого уровня, чем другие варианты, представленные здесь, поэтому вы можете найти их более удобными для ваших нужд. Тем не менее, я думаю, что асинхронные семафоры стоит знать, даже если вы используете абстракции более высокого уровня на практике.

Это выглядит примерно так:

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 так что он возвращает объект с inc а такжеdec методы вместо этого, как в статье в Википедии; это просто, как я это делаю.)

И это все, что вам нужно сделать, чтобы создать асинхронный семафор.

Вот пример его использования. Вы можете определить функцию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 будет вызван.

NB. Как вы можете использовать асинхронные семафоры, зависит от архитектуры вашего кода и ваших собственных требований; то, что я изложил, может быть не совсем то, что вы хотите. Я просто пытаюсь показать вам, как они работают; Остальное зависит от тебя!

И один совет: если бы вы использовали асинхронные семафоры, я бы порекомендовал вам скрыть их создание и вызовы busy за высокоуровневыми абстракциями, чтобы вы не засоряли код приложения низкоуровневыми деталями.

Я посещаю девять лет в будущем, чтобы сказать всем, кто найдет эту ветку, заглянуть в AsyncIterable и «ожидать (...)». Я думаю, что эта техника, основанная на обещаниях, обеспечит адекватное решение.

Вот пример AsyncIterable, который по сути представляет собой очередь функций, которую искал вопрошающий.

ПРИМЕЧАНИЕ. В другом коде, который у меня есть, я использовал этот процесс, но вместо очереди функций я использовал одну функцию и набор путей к файлам. Это позволяет нам работать с гораздо меньшим объемом памяти, но требует специального кода в объекте AsyncIterator. Вопрос заключался в поиске общей очереди функций. Преимущество такого взгляда на проблему заключается в том, что очередь функций не просто допускает один тип вычислений, скорее вы можете поместить в нее любую функцию, которая вам нравится, и выполнять их одну за другой.

Поместите оба фрагмента кода в один файл, чтобы увидеть код в работе. Я установил таймер, чтобы показать пример остановки выполнения через 2 секунды.

Другие вопросы по тегам