Languages
[Edit]
EN

JavaScript - how to make buffered async loop class with next iteration confirmation?

4 points
Created by:
Greg
6440

In this article, we're going to have a look at how to write own asynchronous buffered loop class  (window loop class) that uses callback functions to execute next iterations in proper time or breaks loop - processes asynchronous tasks as resources are available.

This kind of loop is useful when we have to make some iterations and shedule them in groups, e.g. optimisation that has to execute 50 computations but available are only 5 machines and we don't want to make new interations until machine is avaialble.

Note: read this article to see simple async loop with next iteration confirmation example.

1. Custom class example

This implementation uses setTimeout function to call iterations in proper time. To be sure that next iteration will be executed it is necessary to call resume() method in each onIteration method. To break loop it is just necessary to call finish() method.

Note: in below example we shedule next iterations after 1s and break loop after 10 iterations to show how it works.

// ONLINE-RUNNER:browser;

function AsyncLoop(index, count, buffer, onIteration, onFinished) {
    var STOPPED = 0; // loop is stopped
    var STARTED = 1; // loop is started
    var AVAILABLE = 2; // loop is started and ready to execute next iterations
    var ITERATING = 3; // loop is progressing iterations
    var FINISHING = 4; // loop is finishing

    var self = this;

    var i, c, b; // executed indexes, confirmed indexs, free buffer
    var state = STOPPED;

    function shedule(i) {
        var called = false;
        var cover = function(action) {
            return function() {
                if (called) {
                    throw new Error('Only once callback function can be executed in iteration.');
                }
                called = true;
                c -= 1;
                b += 1;
              	if (state == ITERATING) {
                	state = AVAILABLE;
                }
                action();
            };
        }; 
        var callback = function() {
            if (state == FINISHING) {
                return;
            }
            onIteration(i, cover(resume), cover(finish));
        };
        setTimeout(callback, 0);
    }

    function resume() {
        if (state == STARTED || state == AVAILABLE) {
            if (i < count) {
                state = ITERATING;
                while(i < count && b > 0) {
                    i += 1;
                    b -= 1;
                    shedule(i - 1);
                }
            } else {
                if (c == 0) {
                    state = FINISHING;
                    var callback = function() {
                        state = STOPPED;
                        onFinished(true);
                    };
                    setTimeout(callback, 0);
                }
            }
        }
    }

    function finish() {
        if (state == STARTED || state == AVAILABLE || state == ITERATING) {
            state = FINISHING;
            var callback = function() {
                state = STOPPED;
                onFinished(false);
            };
            setTimeout(callback, 0);
        }
    }

    self.run = function() {
        if (state == STOPPED) {
            i = index;
            c = count - index;
            b = buffer;
            if (onIteration.length > 1) {
                // for onIteration(index, resume, finish)
                state = STARTED;
                resume();
            } else {
                // for onIteration(index)
                state = STARTED;
                try {
                    while(i < count) {
                        i += 1;
                        onIteration(i - 1);
                    }
                } finally {
                    state = STOPPED;
                    if (onFinished) {
                        onFinished(true);
                    }
                }
            }
        }
    };
  
  	self.end = finish;
}

// Helper logic

var t1 = new Date();

function getTime() {
    var t2 = new Date();

    return t2 - t1;
}

// Usage example

function onIteration(i, resume, finish) {
    console.log('[loop iteration ' + i + ', time=' + getTime() + ']');

    // resume() call continues loop
    // finish() call breaks loop

    if (i == 10) {
        console.log('finish() method called...');
        finish();
    } else {
        setTimeout(resume, 1000); // continued after 1s
    }
}

function onFinished(completed) {
    console.log('[loop finished, time=' +  getTime() + '] ' + (completed ? 'completed' : 'forced'));
}

var initialIndex = 0;     // initial iteration number
var iterationsCount = 50; // number of iterations to execute
var bufferSize = 3;       // maximum number of executed iterations in same time

var loop = new AsyncLoop(initialIndex, iterationsCount, bufferSize, onIteration, onFinished);

loop.run();
console.log('[loop started, time=' + getTime() + ']');

 

Native Advertising
🚀
Get your tech brand or product in front of software developers.
For more information Contact us
Dirask - we help you to
solve coding problems.
Ask question.

â€ïžđŸ’» 🙂

Join