01.12.2022 в 15:05
Тим Тоуди
Создание асинхронного исполнителя задач с параллелизмом в JavaScript
Давайте представим, что нам нужно создать асинхронный обработчик задач в javascript со следующим ограничением:
- У исполнителя задач должен быть набор задач (task), которые будут ему переданы.
- Каждая из этих задач будет некоторой асинхронной операцией,
- Исполнитель задач также должен убедиться, что в один момент времени может выполняться только заданное количество задач, а другие задачи продолжают ждать, пока не придет их очередь.
Давайте сначала закодируем решение
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 current = this;
if(this.count<this.concurrency){
this.count++;
if(this.waitList.length>0){
let task = this.waitList.shift();
let id = task.id;
this.currentQueue.push(id);
this.showRunningTasks();
let done = function(){
this.currentQueue.splice(this.currentQueue.indexOf(id),1);
this.showRunningTasks();
this.count = this.count - 1;
this.run();
}.bind(current);
task.task(done);
}
}
}
showRunningTasks(){
let existingQueue = this.currentQueue.join(", ");
document.getElementId("running").innerHTML = existingQueue;
}
}
Давайте разберем этот фрагмент кода построчно:
- Сначала мы передаем флаг
concurrency
, указывающий, сколько задач (task) может выполняться одновременно.waitList
используется для создания списка ожидания задач, которые будут ожидать, пока очередь, называемаяcurrentQueue
, не станет пустой. Переменнаяcount
инициализируется для подсчета количества одновременных задач в данный момент. - Метод
push
используется для отправки задачи вwaitList
, а затем мы выполняем метод запуска. Это просто указывает на то, что задачи необходимо запускать, если очередь пуста. - Интересен метод запуска. Во-первых, он сохраняет контекст задачи благодаря первой строке, обозначенной как
let current = this
- Затем мы проверяем параллелизм, и если в waitList есть элементы, мы просто:
- поместим идентификатор задачи в currentQueue,
- отображаем текущие запущенные задачи с помощью метода
showRunningTasks
, - в глубине метода
done
, который будет использоваться в качестве обратного вызова в задачах, мы просто удаляем задачу с идентификатором из текущей очереди, уменьшаем количество, чтобы уменьшить общее количество одновременных задач, - В конце мы рекурсивно вызываем метод run() для запуска текущей функции. Обратите внимание, здесь контекст остается прежним.
- Наконец, мы привязываем эту функцию к текущему контексту.
- Мы передаем эту функцию в task.task function.
Давайте теперь посмотрим, как можно использовать этот метод.
Используйте следующий фрагмент кода, чтобы увидеть результат.
let runner = new Runner(3);
let task1 = {
id: 1,
task: function(done) {
setTimeout(function() {
console.log("Task 1");
done();
}, 3000)
}
}
let task2 = {
id: 2,
task: function(done) {
setTimeout(function() {
console.log("Task 2");
done();
}, 5000)
}
}
let task3 = {
id: 3,
task: function(done) {
setTimeout(function() {
console.log("Task 3");
done();
}, 4000)
}
}
let task4 = {
id: 4,
task: function(done) {
setTimeout(function() {
console.log("Task 4");
done();
}, 9000)
}
}
runner.push(task1);
runner.push(task2);
runner.push(task3);
runner.push(task4);
Вы увидите результат на экране как Task 1, Task 3, Task 2, Task 4, что и ожидается.