WebWorker - Создание потока выполнения асинхронной задачи (обновлено)

[Обновлено 21 апреля 2021 г.] Я опубликовал новую служебную веб-утилиту с улучшенным способом выполнения функций в рабочем потоке. Ссылка на репо приведена ниже. Пожалуйста, проверьте это.



Современные браузеры день ото дня становятся все более мощными. С приходом компьютеров и мобильных устройств, ориентированных на процессор, ваши браузеры могут обрабатывать тяжелую обработку, графику и анимацию, вы называете это. Но… эти возможности имеют свою цену. Пока движки браузера заняты обработкой тяжелых нагрузок, ваш интерфейс может зависнуть. Это приводит к плохому пользовательскому опыту. Вот где на сцену выходят WebWorkers.

WebWorkers существуют уже некоторое время. Они были разработаны для обеспечения неблокирующего потока выполнения задач. Этот поток можно использовать для вычисления дорогостоящих и трудоемких задач процессора, не затрагивая основной поток браузера. Чтобы узнать больше о веб-воркерах и их различных приложениях, вы можете обратиться к этой статье https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers



Я работал над небольшим фреймворком, который помогал бы организованно запускать задачи на веб-воркерах. Мне нужно было решить следующие проблемы:

1. Простой способ создать и выполнить задачу в рабочем потоке веб-сайта
2. Связь между основным потоком браузера и рабочим потоком веб-сайта
3. Доступ ко всем задачам, выполняемым в веб-рабочем потоке

Теперь, прежде чем мы начнем кодировать, нужно запомнить несколько моментов.

  • Рабочий веб-поток полностью изолирован от основного потока. Он не может получить доступ к окну, документу и домену основного потока.
  • Он предоставляет API для связи между двумя потоками, но можно обмениваться только сериализуемыми данными. Это означает, что мы не можем передавать функции как обратные вызовы.
  • Web Worker предоставляет API postMessage () для обмена сообщениями между основным и рабочим потоком Web.

Начнем с простого примера сложения двух чисел в веб-воркерах.

------ GlobalWorker.js --------
onmessage = function(e){
 var num1 = e.data[0];
 var num2 = e.data[1];
 
 var result = num1 + num2;
 console.log(result)
}
------ Main.js ------
Globals.worker =  new Worker('path/GlobalWorker.js');
Globals.worker.postMessage([5, 6])

GlobalWorker.js - это наш js-файл, который будет выполняться в потоке WebWorker.
Main.js будет выполняться в основном потоке браузера.

Задача 1. Создание глобального объекта WebWorker для добавления новых задач в рабочий поток.

------GlobalWorker.js-------
Worker = {
        init: function(){
            onmessage = this.onMessage;
            this.tasks = {}
        },
        addTask: function(task){
            this.tasks[task.name] = task
        },
        onMessage: function(e){
            var self = Worker
            var name = e.data.name,
            args = e.data.args
            if(self.tasks[name] && self.tasks[name].main){
                self.tasks[name].main(args)
            }
        }
}
function Task(conf){
    var self = this
    if(!('name' in conf && 'main' in conf)){
        throw "Task must have a Name and Main method";
    }
    Object.keys(conf).forEach(function(k){
        self[k] = conf[k];
    })
    return self;
}
--------Main.js--------
Globals.Worker = {
    init: function(){
        this.worker =  new Worker('path/worker.js');
    }
    executeWorkerTask: function(taskName, taskArgs){
        var args = {
            name: taskName,
            args: taskArgs
        }
        this.worker.postMessage(args)
    }
}

Конструкторы задач будут создавать простые объекты задач. У всех задач должно быть имя и свойство основного метода. Свойство «name» действует как идентификатор, и «основной» метод задачи будет выполнен, когда задача будет зарегистрирована из Main.js.

Теперь, когда основная структура готова, давайте создадим простую задачу для сложения 2 чисел.

-----GlobalWorker.js------
var add_task = new Task({
name: 'add',
main: function(a, b){
 console.log(a + b)
});
Worker.addTask(add_task)
------Main.js------
Globals.Worker.executeWorkerTask('add', [5,6])

Задача 2: настройка связи между двумя потоками

Теперь, поскольку мы хотим иметь возможность выполнять несколько задач в рабочем потоке сети, нам понадобится механизм, позволяющий задачам в GlobalWorker.js взаимодействовать с Main.js. Добавим карту слушателей в Main.js. Когда postMessage () вызывается из рабочего потока Web, эти слушатели будут выполнены. Вот код

------GlobalWorker.js--------
Worker = {
        init: function(){
            onmessage = this.onMessage;
            this.tasks = {}
        },
        addTask: function(task){
            this.tasks[task.name] = task
        },
        onMessage: function(e){
            var name = e.data.name,
            args = e.data.args
            if(this.tasks[name] && this.tasks[name].main){
                this.tasks[name].main(args)
            }
        }
}
function Task(conf){
    var self = this
    if(!('name' in conf && 'main' in conf)){
        throw "Task must have a Name and Main method";
    }
    Object.keys(conf).forEach(function(k){
        self[k] = conf[k];
    })
    self.postMessage = function(data){
        postMessage({
            name: self.name,
            data: data 
        })
    }
    return self;
}
var add_task = new Task({
name: 'add',
main: function(a, b){
 this.postMessage(a+b)
});
})
Worker.addTask(add_task)
--------Main.js--------
Globals.Worker = {
   init: function(){
        this.worker =  new Worker('path/worker.js');
  this.worker.onmessage = this.messageListener
  this.listeners = {}
    }
    executeWorkerTask: function(taskName, taskArgs, taskListener){
        var args = {
            name: taskName,
            args: taskArgs
        }
        this.worker.postMessage(args)
  if(taskListener){
            this.listeners[taskName] = taskListener
        }
    },
 messageListener: function(e){
        var name = e.data.name
        var data = e.data.data
        if(this.listeners[name]){
            this.listeners[name](data)
        }
    }
}
Globals.Worker.executeWorkerTask('add', [5,6], function(result){
 console.log(result)
})

Worker.addTask(add_task), который добавит Задачу, которую мы хотим выполнить, в GlobalWorker.js. Globals.Worker.executeWorkerTask(name, args, listenerFunc) выполнит их из Main.js и присоединит функцию слушателя. this.postMessage(a+b) выполнит прослушиватель для задачи «добавить» из списка this.listeners.

Мы можем просто добавлять новые задачи и выполнять их, как показано ниже.

------ GlobalWorker.js-------
var sub_task = new Task({
name: 'sub',
main: function(a, b){
 this.postMessage(a-b)
});
})
var multiply_task = new Task({
name: 'multiply',
main: function(a, b){
 this.postMessage(a*b)
});
})
Worker.addTask(sub_task)
Worker.addTask(multiply_task)
-------- Main.js --------
Globals.Worker.executeWorkerTask('add', [5,6], function(result){
 console.log(result)
})
Globals.Worker.executeWorkerTask('sub', [5,6], function(result){
 console.log(result)
})
Globals.Worker.executeWorkerTask('multiply', [5,6], function(result){
 console.log(result)
})

Удачного кодирования !!

Локеш Патрабе