Другой способ сделать многопоточность и асинхронность в JavaScript
До HTML5 JavaScript допускал выполнение только одного потока на страницу.
Был какой-то хакерский способ симулировать асинхронное выполнение с помощью обработчиков событий Yield , setTimeout()
, setInterval()
, XMLHttpRequest
или (см. В конце этого поста пример с yield и setTimeout()
).
Но теперь в HTML5 мы можем использовать рабочие потоки для распараллеливания выполнения функций. Вот пример использования.
Реальная многопоточность
Многопоточность: рабочие потоки JavaScript
HTML5 представлены потоки Web Worker (см .: совместимость браузеров )
Примечание. IE9 и более ранние версии не поддерживают его.
Эти рабочие потоки являются потоками JavaScript, которые работают в фоновом режиме, не влияя на производительность страницы. Для получения дополнительной информации о Web Worker прочитайте документацию или этого руководства .
Вот простой пример с 3-мя потоками Web Worker, которые считают в MAX_VALUE и показывают текущее вычисленное значение на нашей странице:
//As a worker normally take another JavaScript file to execute we convert the function in an URL: http://stackoverflow.com/a/16799132/2576706
function getScriptPath(foo){ return window.URL.createObjectURL(new Blob([foo.toString().match(/^\s*function\s*\(\s*\)\s*\{(([\s\S](?!\}$))*[\s\S])/)[1]],{type:'text/javascript'})); }
var MAX_VALUE = 10000;
/*
* Here are the workers
*/
//Worker 1
var worker1 = new Worker(getScriptPath(function(){
self.addEventListener('message', function(e) {
var value = 0;
while(value <= e.data){
self.postMessage(value);
value++;
}
}, false);
}));
//We add a listener to the worker to get the response and show it in the page
worker1.addEventListener('message', function(e) {
document.getElementById("result1").innerHTML = e.data;
}, false);
//Worker 2
var worker2 = new Worker(getScriptPath(function(){
self.addEventListener('message', function(e) {
var value = 0;
while(value <= e.data){
self.postMessage(value);
value++;
}
}, false);
}));
worker2.addEventListener('message', function(e) {
document.getElementById("result2").innerHTML = e.data;
}, false);
//Worker 3
var worker3 = new Worker(getScriptPath(function(){
self.addEventListener('message', function(e) {
var value = 0;
while(value <= e.data){
self.postMessage(value);
value++;
}
}, false);
}));
worker3.addEventListener('message', function(e) {
document.getElementById("result3").innerHTML = e.data;
}, false);
// Start and send data to our worker.
worker1.postMessage(MAX_VALUE);
worker2.postMessage(MAX_VALUE);
worker3.postMessage(MAX_VALUE);
<div id="result1"></div>
<div id="result2"></div>
<div id="result3"></div>
Мы видим, что три потока выполняются в параллельном режиме, и печатаем их текущее значение на странице. Они не замораживают страницу, потому что они выполняются в фоновом режиме с разделенными потоками.
Многопоточность: с несколькими фреймами
Другой способ добиться этого - использовать несколько iframes , каждый из которых будет выполнять поток. Мы можем дать iframe некоторые параметры по URL, а iframe может связаться со своим родителем, чтобы получить результат и распечатать его обратно ( iframe должен быть в том же домене).
Этот пример работает не во всех браузерах! iframes обычно выполняется в том же потоке / процессе, что и главная страница (но Firefox и Chromium, кажется, обрабатывают это по-разному).
Поскольку фрагмент кода не поддерживает несколько файлов HTML, я просто предоставлю здесь разные коды:
index.html:
//The 3 iframes containing the code (take the thread id in param)
<iframe id="threadFrame1" src="thread.html?id=1"></iframe>
<iframe id="threadFrame2" src="thread.html?id=2"></iframe>
<iframe id="threadFrame3" src="thread.html?id=3"></iframe>
//Divs that shows the result
<div id="result1"></div>
<div id="result2"></div>
<div id="result3"></div>
<script>
//This function is called by each iframe
function threadResult(threadId, result) {
document.getElementById("result" + threadId).innerHTML = result;
}
</script>
thread.html:
//Get the parameters in the URL: http://stackoverflow.com/a/1099670/2576706
function getQueryParams(paramName) {
var qs = document.location.search.split('+').join(' ');
var params = {}, tokens, re = /[?&]?([^=]+)=([^&]*)/g;
while (tokens = re.exec(qs)) {
params[decodeURIComponent(tokens[1])] = decodeURIComponent(tokens[2]);
}
return params[paramName];
}
//The thread code (get the id from the URL, we can pass other parameters as needed)
var MAX_VALUE = 100000;
(function thread() {
var threadId = getQueryParams('id');
for(var i=0; i<MAX_VALUE; i++){
parent.threadResult(threadId, i);
}
})();
Имитация многопоточности
Однопоточность: эмулировать параллелизм JavaScript с помощью setTimeout ()
«Наивным» способом было бы выполнение функции setTimeout()
одна за другой, например:
setTimeout(function(){ /* Some tasks */ }, 0);
setTimeout(function(){ /* Some tasks */ }, 0);
[...]
Но этот метод не работает , потому что каждая задача будет выполняться одна за другой.
Мы можем симулировать асинхронное выполнение, вызывая функцию рекурсивно, как это:
var MAX_VALUE = 10000;
function thread1(value, maxValue){
var me = this;
document.getElementById("result1").innerHTML = value;
value++;
//Continue execution
if(value<=maxValue)
setTimeout(function () { me.thread1(value, maxValue); }, 0);
}
function thread2(value, maxValue){
var me = this;
document.getElementById("result2").innerHTML = value;
value++;
if(value<=maxValue)
setTimeout(function () { me.thread2(value, maxValue); }, 0);
}
function thread3(value, maxValue){
var me = this;
document.getElementById("result3").innerHTML = value;
value++;
if(value<=maxValue)
setTimeout(function () { me.thread3(value, maxValue); }, 0);
}
thread1(0, MAX_VALUE);
thread2(0, MAX_VALUE);
thread3(0, MAX_VALUE);
<div id="result1"></div>
<div id="result2"></div>
<div id="result3"></div>
Как видите, этот второй метод очень медленный и замораживает браузер, потому что он использует основной поток для выполнения функций.
Однопоточность: эмуляция параллелизма JavaScript с помощью yield
Выход - это новая функция в ECMAScript 6 , она работает только на самой старой версии Firefox и Chrome (в Chrome необходимо включить Экспериментальный JavaScript появляется в chrome: // flags / # enable-javascript-harmony ).
Ключевое слово yield приводит к приостановке выполнения функции генератора, и значение выражения, следующего за ключевым словом yield, возвращается вызывающей стороне генератора. Его можно рассматривать как генераторную версию ключевого слова return.
Генератор позволяет приостановить выполнение функции и возобновить ее позже. Генератор может быть использован для планирования ваших функций с помощью техники под названием trampolining .
Вот пример:
var MAX_VALUE = 10000;
Scheduler = {
_tasks: [],
add: function(func){
this._tasks.push(func);
},
start: function(){
var tasks = this._tasks;
var length = tasks.length;
while(length>0){
for(var i=0; i<length; i++){
var res = tasks[i].next();
if(res.done){
tasks.splice(i, 1);
length--;
i--;
}
}
}
}
}
function* updateUI(threadID, maxValue) {
var value = 0;
while(value<=maxValue){
yield document.getElementById("result" + threadID).innerHTML = value;
value++;
}
}
Scheduler.add(updateUI(1, MAX_VALUE));
Scheduler.add(updateUI(2, MAX_VALUE));
Scheduler.add(updateUI(3, MAX_VALUE));
Scheduler.start()
<div id="result1"></div>
<div id="result2"></div>
<div id="result3"></div>