Languages
[Edit]
EN

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

4 points
Created by:
Greg
1514

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() + ']');

 

Hey ūüĎč
Would you like to know what we do?
  • Dirask is online IT community for professionals and hobbyist to share their knowledge and help each other in extraordinary easy way.
  • We welcome everyone
    no matter what the experience,
    no matter how basic the question is,
    this community will help you.