【问题标题】:Semaphore-like queue in javascript?javascript中的类似信号量的队列?
【发布时间】:2013-07-05 21:50:28
【问题描述】:

我有一个变量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 中并不真正需要信号量。

那么,这里用什么?

编辑:我已经编写了“函数队列”,但正如这里所见,我并不真正关心顺序。

【问题讨论】:

  • 您是否在使用任何库,例如 jQuery 或 Dojo?它们具有与您的使用相匹配的延迟功能。
  • 你需要了解promise。请参阅 Q 或 jQuery 的延迟。
  • @karel -- 请不要编辑这样的问题...
  • 同意@Neal,如果你想用你今天使用的东西发布一个自我回答。在问题本身中回答问题并不是一个好主意。
  • 另外@karel 我更新了我的答案以使用 es6。如果对您有帮助,请记住选择已接受的答案:-)

标签: javascript


【解决方案1】:

这是一个不错的 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 Promises:

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

  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/

【讨论】:

  • add_function 应该是 addFunction 或只是 addpush。您还可以引入unshift 将函数添加到队列的开头。
  • @Shmiddty 太需要了 :-P 我认为命名 fn add_function 没有任何问题,这完全取决于您的命名约定。是的,可以为其他功能添加其他方法,但对于这个答案,它们不是必需的。
  • 我觉得 javascript 中的非驼峰式名称是违反直觉的,因为语言本身对所有内容都使用驼峰式。
  • @Shmiddty 另外,这是 Crockford 的标准、雅虎的、谷歌的、微软的以及我读过的几乎每一个标准。
  • 命名约定的适当性完全取决于您所使用的编程语言。最终目标是让您不必记住是add_function 还是addFunction。由于该语言在 camelCase 中完成所有操作,因此您将首先尝试直观地尝试。
【解决方案2】:

我不确定在纯 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 事件兼容,进而与其他 Deferred 对象兼容,因此您可以创建复杂的链,在 Ajax 上触发事件完成,或在满足多个条件后触发“完成”回调。这当然是更高级的功能,但放在你的后兜里也不错。

【讨论】:

【解决方案3】:

除了这里的其他有用的答案,如果你不 需要这些解决方案提供的额外功能,然后 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 以便它返回一个带有 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's 计数器将设置为零,run_me_asap 将是 调用。

注意如何使用异步信号量取决于 关于您的代码架构和您自己的要求; 我列出的可能不是你想要的。我只是 试图向您展示它们是如何工作的;剩下的就看你自己了!

还有一点建议:如果您要使用异步 信号量然后我建议你隐藏他们的创作 以及在更高级别抽象后面对busy 的调用,所以 你不会乱扔你的应用程序代码 底层细节。

【讨论】:

    猜你喜欢
    • 2010-10-15
    • 1970-01-01
    • 2021-03-01
    • 2017-04-18
    • 1970-01-01
    • 2012-10-18
    • 2011-07-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多