Подтвердить что ты не робот

Как сделать неблокирующий код 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. Пожалуйста, перечитайте вопрос, прежде чем отмечать его как дубликат.

4b9b3361

Ответ 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;
    }   
}