Как сделать неблокирующий код javascript?
Как я могу сделать простой, неблокированный вызов функции Javascript? Например:
//begin the program
console.log('begin');
nonBlockingIncrement(10000000);
console.log('do more stuff');
//define the slow function; this would normally be a server call
function nonBlockingIncrement(n){
var i=0;
while(i<n){
i++;
}
console.log('0 incremented to '+i);
}
выходы
"beginPage"
"0 incremented to 10000000"
"do more stuff"
Как я могу сформировать этот простой цикл для асинхронного выполнения и вывода результатов с помощью функции обратного вызова? Идея состоит в том, чтобы не блокировать "делать больше вещей":
"beginPage"
"do more stuff"
"0 incremented to 10000000"
Я пробовал следующие руководства по обратным вызовам и продолжениям, но все они, похоже, полагаются на внешние библиотеки или функции. Ни один из них не отвечает на вопрос в вакууме: как написать Javascript-код, чтобы быть неблокирующим??
Я очень сильно искал этот ответ, прежде чем спрашивать; пожалуйста, не думайте, что я не смотрел. Все, что я нашел, это Node.js specific ([1], [2], [3], [4], [5]) или иным образом относится к другим функциям или библиотекам ([6], [7], [8], [9], [10], [11]), особенно JQuery и setTimeout()
. Пожалуйста, помогите мне написать неблокирующий код с помощью Javascript, а не с помощью Javascript-инструментов, таких как JQuery и Node. Пожалуйста, перечитайте вопрос, прежде чем отмечать его как дубликат.
Ответы
Ответ 1
SetTimeout с обратными вызовами - это путь. Хотя, понимайте, что ваши области действия не такие же, как в С# или другой многопоточной среде.
Javascript не ждет завершения обратного вызова функции.
Если вы скажете:
function doThisThing(theseArgs) {
setTimeout(function (theseArgs) { doThatOtherThing(theseArgs); }, 1000);
alert('hello world');
}
Ваше предупреждение будет срабатывать, прежде чем переданная вами функция будет.
Отличие в том, что предупреждение заблокировало поток, но ваш обратный вызов не сделал.
Ответ 2
Чтобы сделать ваш цикл неблокирующим, вы должны разбить его на разделы и разрешить циклу обработки событий JS потреблять пользовательские события перед тем, как перейти к следующему разделу.
Самый простой способ добиться этого - выполнить определенный объем работы, а затем использовать setTimeout(..., 0)
для очереди следующего фрагмента работы. Существенно, что эта очередь позволяет циклу событий JS обрабатывать любые события, которые были поставлены в очередь в то же время, прежде чем перейти к следующему произведению:
function yieldingLoop(count, chunksize, callback, finished) {
var i = 0;
(function chunk() {
var end = Math.min(i + chunksize, count);
for ( ; i < end; ++i) {
callback.call(null, i);
}
if (i < count) {
setTimeout(chunk, 0);
} else {
finished.call(null);
}
})();
}
с использованием:
yieldingLoop(1000000, 1000, function(i) {
// use i here
}, function() {
// loop done here
});
См. http://jsfiddle.net/alnitak/x3bwjjo6/ для демонстрации, где функция callback
просто устанавливает переменную в текущий счетчик итераций, а отдельный цикл на основе setTimeout
опросит текущее значение этой переменной и обновляет страницу с ее значением.
Ответ 3
Вы не можете выполнить две петли одновременно, помните, что JS - это единственный поток.
Таким образом, выполнение этого никогда не будет работать
function loopTest() {
var test = 0
for (var i; i<=100000000000, i++) {
test +=1
}
return test
}
setTimeout(()=>{
//This will block everything, so the second won't start until this loop ends
console.log(loopTest())
}, 1)
setTimeout(()=>{
console.log(loopTest())
}, 1)
Если вы хотите добиться многопоточного потока, вам нужно использовать веб-рабочих, но они должны иметь выделенный файл js, и вы можете передавать им только те объекты.
Но Мне удалось использовать Web Workers без разделяемых файлов, создав файлы Blob, и я также могу передать их функции обратного вызова.
//A fileless Web Worker
class ChildProcess {
//@param {any} ags, Any kind of arguments that will be used in the callback, functions too
constructor(...ags) {
this.args = ags.map(a => (typeof a == 'function') ? {type:'fn', fn:a.toString()} : a)
}
//@param {function} cb, To be executed, the params must be the same number of passed in the constructor
async exec(cb) {
var wk_string = this.worker.toString();
wk_string = wk_string.substring(wk_string.indexOf('{') + 1, wk_string.lastIndexOf('}'));
var wk_link = window.URL.createObjectURL( new Blob([ wk_string ]) );
var wk = new Worker(wk_link);
wk.postMessage({ callback: cb.toString(), args: this.args });
var resultado = await new Promise((next, error) => {
wk.onmessage = e => (e.data && e.data.error) ? error(e.data.error) : next(e.data);
wk.onerror = e => error(e.message);
})
wk.terminate(); window.URL.revokeObjectURL(wk_link);
return resultado
}
worker() {
onmessage = async function (e) {
try {
var cb = new Function(`return ${e.data.callback}`)();
var args = e.data.args.map(p => (p.type == 'fn') ? new Function(`return ${p.fn}`)() : p);
try {
var result = await cb.apply(this, args); //If it is a promise or async function
return postMessage(result)
} catch (e) { throw new Error(`CallbackError: ${e}`) }
} catch (e) { postMessage({error: e.message}) }
}
}
}
setInterval(()=>{console.log('Not blocked code ' + Math.random())}, 1000)
console.log("starting blocking synchronous code in Worker")
console.time("\nblocked");
var proc = new ChildProcess(blockCpu, 43434234);
proc.exec(function(block, num) {
//This will block for 10 sec, but
block(10000) //This blockCpu function is defined below
return `\n\nbla bla ${num}\n` //Captured in the resolved promise
}).then(function (result){
console.timeEnd("\nblocked")
console.log("End of blocking code", result)
})
.catch(function(error) { console.log(error) })
//random blocking function
function blockCpu(ms) {
var now = new Date().getTime();
var result = 0
while(true) {
result += Math.random() * Math.random();
if (new Date().getTime() > now +ms)
return;
}
}