带有等待功能的 JavaScript 简单任务运行程序

Roy*_*Roy 5 javascript async-await ecmascript-6 es6-promise

我想实现类似任务运行程序的东西,它将被推送新任务。这些任务中的每一个都可以是一些异步操作,例如等待用户或进行 API 调用或其他操作。任务运行程序确保一次只能执行允许数量的任务,而其他任务将继续等待,直到轮到它们。

class Runner {
  constructor(concurrent) {
    this.taskQueue = []; //this should have "concurrent" number of tasks running at any given time

  }

  push(task) {
    /* pushes to the queue and then runs the whole queue */
  }
}
Run Code Online (Sandbox Code Playgroud)

调用模式是

let runner = new Runner(3);
runner.push(task1);
runner.push(task2);
runner.push(task3);
runner.push(task4);
Run Code Online (Sandbox Code Playgroud)

其中任务是一个函数引用,它将在最后运行一个回调,我们可以知道它已完成。所以它应该像

let task = function(callback) {
  /* does something which is waiting on IO or network or something else*/
  callback(); 
}
Run Code Online (Sandbox Code Playgroud)

所以我正在推动跑步者的关闭,比如

runner.push(function(){return task(callback);});
Run Code Online (Sandbox Code Playgroud)

我想我可能还需要添加一个 waitList 队列。但任务本身并不是承诺,所以我不知道如何检查这些任务是否完成。

无论如何,我需要正确的方法。

Roy*_*Roy 7

该概念的简单演示。更改变量名称以便更好地理解。

class Runner {
  constructor(concurrency = 1) {
    this.concurrency = concurrency;
    this.waitList = [];
    this.count = 0;
    this.currentQ = [];
  }

  push(task) {
    this.waitList.push(task);
    this.run();
  }

  run() {
    let me = this;
    if (this.count < this.concurrency) {
      this.count++;
      if (this.waitList.length > 0) {
        let task = this.waitList.shift();
        let id = task.id;
        this.currentQ.push(id);
        this.showQ();
        task.task(function() {
          this.currentQ.splice(this.currentQ.indexOf(id), 1);
          this.showQ();
          this.count--;
          this.run();
        }.bind(me))
      }
    }
  }

  showQ() {
    let q = "";
    q = this.currentQ.join(', ');
    document.getElementById("running").innerHTML = q;
  }
}

let output = document.getElementById("output");

let task1 = {
  id: 1,
  task: function(done) {
    let div = document.createElement("div");
    let node = document.createTextNode("Picking up Task1");
    div.appendChild(node);
    output.appendChild(div);
    setTimeout(function() {
      div = document.createElement("div");
      node = document.createTextNode("Finished Task1");
      div.appendChild(node);
      output.appendChild(div);
      done()
    }, 3000)
  }
}
let task2 = {
  id: 2,
  task: function(done) {
    let div = document.createElement("div");
    let node = document.createTextNode("Picking up Task2");
    div.appendChild(node);
    output.appendChild(div);
    setTimeout(function() {
      div = document.createElement("div");
      node = document.createTextNode("Finished Task2");
      div.appendChild(node);
      output.appendChild(div);
      done()
    }, 6000)
  }
}
let task3 = {
  id: 3,
  task: function(done) {
    this.id = "3";
    let div = document.createElement("div");
    let node = document.createTextNode("Picking up Task3");
    div.appendChild(node);
    output.appendChild(div);
    setTimeout(function() {
      div = document.createElement("div");
      node = document.createTextNode("Finished Task3");
      div.appendChild(node);
      output.appendChild(div);
      done()
    }, 10000)
  }
}
let task4 = {
  id: 4,
  task: function(done) {
    this.id = "4";
    let div = document.createElement("div");
    let node = document.createTextNode("Picking up Task4");
    div.appendChild(node);
    output.appendChild(div);
    setTimeout(function() {
      div = document.createElement("div");
      node = document.createTextNode("Finished Task4");
      div.appendChild(node);
      output.appendChild(div);
      done()
    }, 5000)
  }
}
let task5 = {
  id: 5,
  task: function(done) {
    this.id = "5";
    let div = document.createElement("div");
    let node = document.createTextNode("Picking up Task5");
    div.appendChild(node);
    output.appendChild(div);
    setTimeout(function() {
      div = document.createElement("div");
      node = document.createTextNode("Finished Task5");
      div.appendChild(node);
      output.appendChild(div);
      done()
    }, 6000)
  }
}
let task6 = {
  id: 6,
  task: function(done) {
    this.id = "6";
    let div = document.createElement("div");
    let node = document.createTextNode("Picking up Task6");
    div.appendChild(node);
    output.appendChild(div);
    setTimeout(function() {
      div = document.createElement("div");
      node = document.createTextNode("Finished Task6");
      div.appendChild(node);
      output.appendChild(div);
      done()
    }, 4000)
  }
}
let task7 = {
  id: 7,
  task: function(done) {
    this.id = "7";
    let div = document.createElement("div");
    let node = document.createTextNode("Picking up Task7");
    div.appendChild(node);
    output.appendChild(div);
    setTimeout(function() {
      div = document.createElement("div");
      node = document.createTextNode("Finished Task7");
      div.appendChild(node);
      output.appendChild(div);
      done()
    }, 5000)
  }
}

let r = new Runner(3);
r.push(task1);
r.push(task2);
r.push(task3);
r.push(task4);
r.push(task5);
r.push(task6);
r.push(task7);
Run Code Online (Sandbox Code Playgroud)
Currently running
<div id="running">

</div>
<hr>
<div id="output">

</div>
Run Code Online (Sandbox Code Playgroud)


tra*_*r53 2

所以我正在推动跑步者的关闭,比如

runner.push(function(){return task(callback);});
Run Code Online (Sandbox Code Playgroud)

看起来运行程序的缺失部分被添加到调用语法中。一个更完整的跑步者可能看起来像:

class Runner {
  constructor(concurrent) {
    this.taskQueue = []; // run at most "concurrent" number of tasks at once
    this.runCount = 0;
    this.maxCount = concurrent;
    this.notifyEnd = this.notifyEnd.bind(this);
  }
  
  notifyEnd() {
    --this.runCount;
    this.run();
  }

  run() {
    while( (this.runCount < this.maxCount) && taskQueue.length) {
      ++this.runCount;
      // call task with callback bound to this instance (in the constructor)
      taskQueue.shift()(this.notifyEnd);
    } 
  }

  push(task) {
    this.taskQueue.push(task);
    this.run();
  }
}
Run Code Online (Sandbox Code Playgroud)

push现在,使用带有回调参数的函数来调用运行器的方法。运行器状态包含在 的值中runCount,0 表示空闲,或者正整数表示任务正在运行。

仍然存在几个问题:

  1. 可以同步调用该任务以将其添加到运行器的代码。then它缺乏 Promises 始终从事件队列异步调用回调的严格方法。

  2. 任务代码必须正常返回,没有错误。这在 JavaScript 中并非闻所未闻,其中未捕获的承诺拒绝错误的主机跟踪器必须执行相同的操作,但在应用程序脚本中相当不寻常。运行程序对任务的调用可以放置在一个try/catch块中以捕获同步错误,但如果在任务引发同步错误之前收到回调,则还应该添加代码以忽略错误 - 否则正在运行的任务计数可能会出错。

  3. 如果任务多次调用回调,则上面的运行程序中正在运行的任务计数将被打乱。

Promise 接口的开发和标准化背后也有类似的考虑。我建议在考虑到潜在的缺点后,如果一个简单的任务运行程序满足所有要求,那么就使用一个。如果需要额外的稳健性,那么承诺任务并编写一个更加以承诺为中心的运行程序可能是更好的选择。