среда, 23 апреля 2014 г.

Мой JavaScript Router

Внимание!!! Код не проверен!

require.config({
    paths: {
          jquery: 'lib/jquery/jquery'
    }
});

define(['jquery'], function($) {

    // Маршрутизатор
   
    // Добавить URL в маршрутизатор
    // router.add('/edit/user', function(){alert('User.');});
   
    // Добавить сразу несколько URL в маршрутизатор
    // router.add({
    //        '/edit/user': function(){alert('User.');}
    //      , '/update/user':  function(){alert('Update.');}
    //  });
   
    // Добавить URL с переменным параметром
    // router.add('/edit/user/:id', function(id){alert(id);});
   
    // Добавить URL с переменным содержимым
    // router.add('/edit/user/*', function(somethingInURL){alert(somethingInURL);});
   
    // Изменить URL в маршрутизаторе
    // router.update('/edit/user', function(){alert('User.');});
   
    // Изменить сразу несколько URL в маршрутизаторе
    // router.update({
    //        '/edit/user': function(){alert('User.');}
    //      , '/update/user':  function(){alert('Update.');}
    //  });

    // Удалить URL из маршрутизатора
    // router.remove('/edit/user');
   
    // Удалить сразу несколько URL из маршрутизатора
    // router.remove('/edit/user', '/update/user');

    // Перейти по конкретному URL
    // router.go('/edit/user');
   
    // Начать отслеживание изменения URL в адресной строке
    // router.start();
   
    // Остановить отслеживание изменения URL в адресной строке
    // router.stop();

    var router = {

        // Массив для хранения маршрутов URL
       
          routes: []
       
        // Добавление маршрута URL и функции, вызываемой при переходе на него, в массив для хранения маршрутов
       
        , add: function (path, callback) {
            var i
                , len
                , notFoundPath = true;
            if (Object.prototype.toString.call(path) === '[object Object]') {
                for (var key in path) {
                    router.add(key, path[key]);
                }
            } else {
                if (Object.prototype.toString.call(path) === '[object String]') {    
                    path = path
                            .replace(/[-[\]{}()+?.,\\^$|#\s]/g, '\\$&') // ESC
                            .replace(/:([\w\d]+)/g, '([^\/]*)') // ARG_NAMED
                            .replace(/\*([\w\d]+)*/g, '(.*?)'); // ARG_SPLAT
                    path = new RegExp('^' + path + '$');
                }
                for (i = 0, len = router.routes.length; i < len; i++) {
                    if (router.routes[i].path === path) {
                        router.routes[i].path = path;
                        router.routes[i].callback = callback;
                        notFoundPath = false;
                        break;
                    }
                }
                if (notFoundPath) {
                    router.routes.push({
                          'path': path
                        , 'callback': callback
                    });
                }
            }
          }
         
        // Изменение в массиве для хранения маршрутов функции маршрута URL, вызываемой при переходе на него
       
        , update: function (path, callback) {
            var i
                , len
                , notFoundPath = true;
            if (Object.prototype.toString.call(path) === '[object Object]') {
                for (var key in path) {
                    router.add(key, path[key]);
                }
            } else {
                if (Object.prototype.toString.call(path) === '[object String]') {    
                    path = path
                            .replace(/[-[\]{}()+?.,\\^$|#\s]/g, '\\$&') // ESC
                            .replace(/:([\w\d]+)/g, '([^\/]*)') // ARG_NAMED
                            .replace(/\*([\w\d]+)*/g, '(.*?)'); // ARG_SPLAT
                    path = new RegExp('^' + path + '$');
                }
                for (i = 0, len = router.routes.length; i < len; i++) {
                    if (router.routes[i].path === path) {
                        router.routes[i].path = path;
                        router.routes[i].callback = callback;
                        notFoundPath = false;
                        break;
                    }
                }
                if (notFoundPath) {
                    router.routes.push({
                          'path': path
                        , 'callback': callback
                    });
                }
            }
          }
         
        // Удаление маршрута URL и функции, вызываемой при переходе на него, из массива для хранения маршрутов
       
        , remove: function () {
            var tempArray = []
                , argsLength = arguments.length
                , routesLength
                , path
                , i;
            if (argsLength > 0) {
                for (;argsLength--;) {
                    path = arguments[argsLength];
                    path = path
                            .replace(/[-[\]{}()+?.,\\^$|#\s]/g, '\\$&') // ESC
                            .replace(/:([\w\d]+)/g, '([^\/]*)') // ARG_NAMED
                            .replace(/\*([\w\d]+)*/g, '(.*?)'); // ARG_SPLAT
                    path = new RegExp('^' + path + '$');
                    for (i = 0, routesLength = router.routes.length; i < routesLength; i++) {
                        if (router.routes[i].path !== path) {
                            tempArray.push(router.routes[i]);
                        }
                    }
                    router.routes = tempArray;
                }
            }
          }
         
        // Настройка кэширования содержимого и определение поддержки браузером history API
         
        , history: {
              cache: false
            , support: ('history' in window)
          }
         
        // Запуск маршрутизатора для начала отслеживания изменений URL в адресной строке
       
        , start: function () {
            if (router.history.cache) {
                setTimeout(function(){ // Хак для браузеров на основе WebKit
                    $(window).bind('popstate', router.change);
                }, 0);
            } else {
                $(window).bind('hashchange', router.change);
            }
            router.change();
          }
         
        // Остановить отслеживание изменения URL в адресной строке
       
        , stop: function () {
            if (router.history.cache) {
                setTimeout(function(){ // Хак для браузеров на основе WebKit
                    $(window).unbind('popstate', router.change);
                }, 0);
            } else {
                $(window).unbind('hashchange', router.change);
            }
          }

        // Текущий URL в адресной строке
       
        , currentPath: null
       
        // Функция перехода по произвольному URL
       
        , go: function (path) {
            if (router.history.cache) {
                history.cache.pushState({}, document.title, router.getHost() + path);
            } else {
                window.location.hash = path;
            }
          }
         
        // Функция для обработки собтия изменения URL в адресной строке
       
        , change: function() { // Здесь важно ссылаться на router, а не на this, так как метод change вызывается в контексте window, а не в контексте router
            var path = router.history.cache ? router.getPath() : router.getFragment()
                , routesLength = router.routes.length
                , route
                , i;
            if (path === router.currentPath) {return;}
            router.currentPath = path;
            for (i = 0; i < routesLength; i++) {
                route = router.routes[i];
                if (router.match(path, route.path, route.callback)) {return;} // Провести сопоставление текущего URL с маршрутом из массива routes
            }
          }
         
        // Функция для сопоставления текущего URL с маршрутом из массива routes
         
        , match: function (path, routePath, routeCallback) {
            var match = routePath.exec(path) // Выполнить регулярное выражение из маршрута routes для данного пути
                , params;
            if (!match) {return false;}
            params = match.slice(1);
            routeCallback.apply(routeCallback, params);
            return true;
          }

        // Методы для получения различных частей адреса URL
       
        , getPath: function() {
            return window.location.pathname; // Результат: /edit/user/5
          }

        , getHash: function() {
            return window.location.hash; // Результат: #comment-25
          }

        , getHost: function() {
            return ('' + window.location).replace(router.getPath() + router.getHash(), ''); // Результат: http://site.com
          }

        , getFragment: function() {
            return router.getHash().replace(/^#!*/, ''); // Результат: comment-25
          }

    };

    return router;
   
});

JavaScript Singleton - хороший пример

Cоздадим реализацию Singleton, которая:
- инкапсулирует данные
- не создает объект, если конструктор не был вызван
- позволяет использовать все инструменты прототипного наследования (constructor, Singleton.prototype, instanceof)
- может вызываться как с new, так и без

var Singleton = (function () {
    var instance;

    return function ConstructSingleton () {
        if (instance) {
            return instance;
        }
        if (this && this.constructor === ConstructSingleton) {
            instance = this;
        } else {
            return new ConstructSingleton();
        }
    }
})();

Разберем что происходит в возвращаемой функции.

Если есть instance, то возвращаем его.

Если функция используется с new или без, то все равно мы вернем ссылку на объект, сконструированный этой функцией.

Если функция вызвана с new, то this ссылается на объект, конструируемый этой функцией.
this так же может быть определен, если функция вызвана в нестрогом режиме (this === window), или, если функция вызвана как метод объекта (this === этот_объект). Второй случай аннигилируется проверкой конструктора у this.

Если this не определен (вызов в строгом режиме) или конструктор у this не текущая функция, возвращаем результат текущей функции с new.

Имеем на выходе пуленепробиваемый конструктор синглтона, который можно где угодно хранить и как угодно вызывать.

Набор других паттернов можно найти в хранилище github.com/podgorniy/javascript-toolbox

четверг, 17 апреля 2014 г.

JavaScript (3).times(function(){});

if (!Number.prototype.times) {
    Number.prototype.times = function (callback) {
        var counter = this
           , i;
        if (Object.prototype.toString.call(counter) !== '[object Number]') {
            throw new Error('Counter in "times" function must be number.');
        }
        counter = Math.abs(parseInt(counter, 10));
        if (counter === 0) {return;}
        counter++;
        for (i = 1; i < counter; i++) {
            callback(i);
        }
    };
}

console.log('First test');
(0).times(function(i){console.log(i);});

console.log('Second test');
(1).times(function(i){console.log(i);});

console.log('Third test');
(2).times(function(i){console.log(i);});

пятница, 11 апреля 2014 г.

Библиотека для выполнения асинхронных функций when - then, циклов for, сортировки массивов sort и reverse - Async.js

;(function (name, context, definition) {
    if (typeof module !== 'undefined' && module.exports) {
        module.exports = definition();
    } else if (typeof define === 'function' && define.amd) {
        define(name, definition);
    } else {
        context[name] = definition();
    }
})('async', this, function () {

    // setImmediate polyfill для использования setImmediate в асинхронных циклах
    (function (global, undefined) {
        'use strict';

        var tasks = (function () {
       
            function Task(handler, args) {
                this.handler = handler;
                this.args = args;
            }
           
            Task.prototype.run = function () {
                var scriptSource;
                // Смотри шаги в секции 5 спецификации.
                if (typeof this.handler === 'function') {
                    // Аргумент "this.args" не представлен в спецификации setImmediate,
                    // хотя "undefined" представлен в спецификации setTimeout:
                    // http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html
                    this.handler.apply(undefined, this.args);
                } else {
                    scriptSource = '' + this.handler;
                    /* jshint evil: true */
                    eval(scriptSource);
                }
            };

            var nextHandle = 1 // В спецификации указано: больше нуля
                , tasksByHandle = {}
                , currentlyRunningATask = false;

            return {
                  addFromSetImmediateArguments: function (args) {
                        var handler = args[0]
                            , argsToHandle = Array.prototype.slice.call(args, 1)
                            , task = new Task(handler, argsToHandle)
                            , thisHandle = nextHandle++;
                        tasksByHandle[thisHandle] = task;
                        return thisHandle;
                  }
                , runIfPresent: function (handle) {
                        // Из спецификации: "Дождитесь пока любые вызовы этого алгоритма, начатые до этого момента, завершатся."
                        // Поэтому, если мы запускаем задание, то мы должны сделать задержку перед его вызовом.
                        var task;
                        if (!currentlyRunningATask) {
                            task = tasksByHandle[handle];
                            if (task) {
                                currentlyRunningATask = true;
                                try {
                                    task.run();
                                } catch (e) {
                                } finally {
                                    delete tasksByHandle[handle];
                                    currentlyRunningATask = false;
                                }
                            }
                        } else {
                            // Делаем задержку посредством setTimeout.
                            // Была попытка использовать вместо этого setImmediate,
                            // но в  Firefox 7 это приводило к возникновению ошибки "too much recursion".
                            global.setTimeout(function(){tasks.runIfPresent(handle);}, 0);
                        }
                  }
                , remove: function (handle) {
                        delete tasksByHandle[handle];
                  }
            };
        })();

        function canUseNextTick() {
            // Не дай себя обмануть окружению browserify.
            return typeof process === 'object' && Object.prototype.toString.call(process) === '[object process]';
        }

        function canUseMessageChannel() {
            return !!global.MessageChannel;
        }

        function canUsePostMessage() {
            // Проверка на наличие "importScripts" предотвращает имплементацию внутри web worker,
            // там где "global.postMessage" означает кое-что принципиально другое и не может использоваться для данного назначения.
            if (!global.postMessage || global.importScripts) {
                return false;
            }
            var postMessageIsAsynchronous = true
                , oldOnMessage = global.onmessage;
            global.onmessage = function () {postMessageIsAsynchronous = false;};
            global.postMessage('', '*');
            global.onmessage = oldOnMessage;
            return postMessageIsAsynchronous;
        }

        function canUseReadyStateChange() {
            return 'document' in global && 'onreadystatechange' in global.document.createElement('script');
        }

        function installNextTickImplementation(attachTo) {
            attachTo.setImmediate = function () {
                var handle = tasks.addFromSetImmediateArguments(arguments);
                process.nextTick(function () {
                    tasks.runIfPresent(handle);
                });
                return handle;
            };
        }

        function installMessageChannelImplementation (attachTo) {
            var channel = new global.MessageChannel();
            channel.port1.onmessage = function (event) {
                var handle = event.data;
                tasks.runIfPresent(handle);
            };
            attachTo.setImmediate = function () {
                var handle = tasks.addFromSetImmediateArguments(arguments);
                channel.port2.postMessage(handle);
                return handle;
            };
        }

        function installPostMessageImplementation (attachTo) {
            // Устанавливаем обработчик событий на глобальный объект global для отслеживания события типа "message".
            // Смотри документацию по ссылкам:
            // https://developer.mozilla.org/en/DOM/window.postMessage
            // http://www.whatwg.org/specs/web-apps/current-work/multipage/comms.html#crossDocumentMessages
            var MESSAGE_PREFIX = 'com.bn.NobleJS.setImmediate' + Math.random();

            function isStringAndStartsWith(string, putativeStart) {
                return typeof string === 'string' && string.substring(0, putativeStart.length) === putativeStart;
            }

            function onGlobalMessage(event) {
                // Эта функция будет перехватывать все входящие сообщения (даже приходящие из других открытых окон), поэтому необходимо
                // исключить возможность срабатывания обработчика вхолостую. Мы проверяем, что все еще на ходимся в данном окне по
                // наличию случайно сгенерированного значения префикса.
                var handle;
                if (event.source === global && isStringAndStartsWith(event.data, MESSAGE_PREFIX)) {
                    handle = event.data.substring(MESSAGE_PREFIX.length);
                    tasks.runIfPresent(handle);
                }
            }
           
            if (global.addEventListener) {
                global.addEventListener('message', onGlobalMessage, false);
            } else {
                global.attachEvent('onmessage', onGlobalMessage);
            }
            attachTo.setImmediate = function () {
                var handle = tasks.addFromSetImmediateArguments(arguments);
                // Отправляем сообщение через глобальный объект global самому себе с последующей обработкой и идентификацией префикса
                // для запуска функции слушателя onGlobalMessage, описанной выше.
                global.postMessage(MESSAGE_PREFIX + handle, '*');
                return handle;
            };
        }

        function installReadyStateChangeImplementation (attachTo) {
            attachTo.setImmediate = function () {
                var handle = tasks.addFromSetImmediateArguments(arguments);
                // Создать тэг <script>. Его событие readystatechange быдет вызвано 1 раз асинхронно, как только он будет вставлен в документ.
                // Делая так мы ставим задание в очередь. Помним о необходимости удалить тэг <script> после того, как событие будет вызвано.
                var scriptEl = global.document.createElement('script');
                scriptEl.onreadystatechange = function () {
                    tasks.runIfPresent(handle);
                    // Удаляем тэг <script>
                    scriptEl.onreadystatechange = null;
                    scriptEl.parentNode.removeChild(scriptEl);
                    scriptEl = null;
                };
                global.document.documentElement.appendChild(scriptEl);
                return handle;
            };
        }

        function installSetTimeoutImplementation (attachTo) {
            attachTo.setImmediate = function () {
                var handle = tasks.addFromSetImmediateArguments(arguments);
                global.setTimeout(function(){tasks.runIfPresent(handle);}, 0);
                return handle;
            };
        }

        var attachTo;
        if (!global.setImmediate) {
            // Если возможно, то мы должны прикрепить setImmediate к prototype глобального объекта, в котором существует setTimeout
            if (
                      typeof Object.getPrototypeOf === 'function'
                && 'setTimeout' in Object.getPrototypeOf(global)
            ) {
                attachTo = Object.getPrototypeOf(global);
            } else {
                attachTo = global;
            }
            if (canUseNextTick()) {
                installNextTickImplementation(attachTo); // Для Node.js версии ранее 0.9
            } else if (canUsePostMessage()) {
                installPostMessageImplementation(attachTo); // Для современных браузеров, кроме IE 10
            } else if (canUseMessageChannel()) {
                installMessageChannelImplementation(attachTo); // Для web workers, в тех браузерах, которые их поддерживают
            } else if (canUseReadyStateChange()) {
                installReadyStateChangeImplementation(attachTo); // Для IE 6-8
            } else {
                installSetTimeoutImplementation(attachTo); // Для остальных старых браузеров
            }
            attachTo.clearImmediate = tasks.remove;
        }
       
    })(typeof global === 'object' && global ? global : this);
   
    // Асинхронные функции

    var async = {}; // Объект, который будет хранить в себе перечень асинхронных функций
   
    // Асинхронный цикл for
    async.forLoop = function (params) {

        params = params || {}; // Параметры асинхронного цикла
       
        params.init = params.init || {}; // Начальные значения счетчиков цикла
        params.condition = params.condition || function () {return true;}; // Функция, задающая и проверяющая условия выхода из цикла
        params.iterate = params.iterate || function () {}; // Функция, выполняющая приращение счетчиков цикла
        params.body = params.body || function () {}; // Тело цикла
        params.done = params.done || function () {}; // Функция, выполняющаяся после завершения цикла
       
        params.timeout = (params.timeout !== undefined) ? params.timeout : 0; // Время задержки в мс перед началом следующего шага цикла
        // В общем случае для выполнения маленьких задержек рекомендуется использовать значение не менее 25 мс,
        // потому что меньшие задержки оставляют слишком короткие интервалы времени для обновления пользовательского интерфейса.
        // Однако для ускорения выполнения цикла оставлена задержка 0 мс, если время задержки специально не указано.
       
        params.breakFunction = function () {}; // Функция, выполняющаясь при прерывания цикла
        params.continueFunction = function () {}; // Функция, выполняющаясь при перепрыгивании на следующий шаг цикла

        params.breakLoop = function (breakFunction) { // Определение вида прерывания цикла
            if (breakFunction === undefined) {
                return 'break';
            } else {
                params.breakFunction = breakFunction;
                return 'break function';
            }
        };
       
        params.continueLoop = function (continueFunction) { // Определение вида перепрыгивания на следующий шаг цикла
            if (continueFunction === undefined) {
                return 'continue';
            } else {
                params.continueFunction = continueFunction;
                return 'continue function';
            }
        };
       
        function executeBody () { // Функция, выполняющая тело цикла и проверяющая условия прерывания цикла
            switch (params.body(params)) { // Выполнить тело цикла
                case 'break': params.done(params); break; // Прервать цикл
                case 'break function': params.breakFunction(params); break; // Прервать цикл и выполнить функцию, в которой описан переход во внешний цикл
                case 'continue': params.next(); break; // Перепрыгнуть на следующий шаг цикла
                case 'continue function': params.continueFunction(params); break; // Выполнить функцию, в которой описан переход во внешний цикл
            }
        }

        params.counter = 0; // Счетчик числа выполненных шагов цикла

        params.next = function () { // Выполнить шаг цикла
            if (params.counter > 0) {
                params.iterate(params); // Произвести приращение счетчиков цикла при выполнении следующего шага, за исключением первого шага
            }
            params.counter++;
            if (params.condition(params)) { // Проверить тестовое условие выхода из цикла
                if (params.timeout) { // Если время задержки в мс перед началом следующего шага цикла было установлено, то выполнить тело цикла с задержкой
                    setTimeout(executeBody, params.timeout);
                } else { // Если время задержки в мс перед началом следующего шага цикла не было установлено или равно 0, то выполнить тело цикла немедленно
                    setImmediate(executeBody);
                }
            } else {
                params.done(params); // Выполнить завершающую функцию в конце работы цикла
            }
        };
       
        params.next(); // Выполнить проверку тестового условия цикла и затем выполнить первый шаг цикла
       
    };
   
    /* Примеры выполнения асинхронных циклов for
   
    // Пример 1 - Одиночный асинхронный цикл
   
    async.forLoop({
          init: {i: 0}
        , condition: function (loop) {if (loop.init.i < 10) {return true;} else {return false;}}
        , iterate: function (loop) {loop.init.i = loop.init.i + 1;}
        , body: function (loop) {
            console.log(loop.init.i);
            loop.next(); // Переход на следующий шаг цикла
          }
        , done: function (loop) {console.log('The End. Last index: ' + loop.init.i);}
    });
   
    // Пример 2 - Вложенные асинхронные циклы
   
    // Внешний цикл - начало
    async.forLoop({
          init: {i: 0}    
        , condition: function (outerLoop) {if (outerLoop.init.i < 2) {return true;} else {return false;}}  
        , iterate: function (outerLoop) {outerLoop.init.i = outerLoop.init.i + 1;}
        , body: function (outerLoop) {
            // Внутренний цикл - начало
            async.forLoop({
                  init: {j: 0}    
                , condition: function (innerLoop) {if (innerLoop.init.j < 2) {return true;} else {return false;}}  
                , iterate: function (innerLoop) {innerLoop.init.j = innerLoop.init.j + 1;}
                , body: function (innerLoop) {
                    console.log(outerLoop.init.i + ' ' + innerLoop.init.j);
                    innerLoop.next(); // Переход на следующий шаг внутреннего цикла
                  }
                , done: function (innerLoop) {
                    outerLoop.next(); // После завершения внутреннего цикла осуществляется переход на следующий шаг внешнего цикла
                  }
            });
            // Внутренний цикл - конец
          }
        , done: function (outerLoop) {
            console.log('All cycles done');
          }
    });
    // Внешний цикл - конец
   
    // Пример 3 - Прерывание одиночного асинхронного цикла
   
    async.forLoop({
          init: {i: 0}
        , condition: function (loop) {if (loop.init.i < 10) {return true;} else {return false;}}
        , iterate: function (loop) {loop.init.i = loop.init.i + 1;}
        , body: function (loop) {
            if (loop.init.i === 3) {
                // Перепрыгивание на следующий шаг цикла
                return loop.continueLoop(
                // Если в качестве метки label будет передана функция, то будет выполнен её код
                // function(loop) {
                //     console.log('Continue label. Current index: ' + loop.init.i);
                //     loop.done(loop); // В результате перепрыгивания шага можно просто завершить выполнение цикла
                // }
                );
            }
            if (loop.init.i === 5) {
                // Прерывание цикла
                return loop.breakLoop(
                // Если в качестве метки label будет передана функция, то будет выполнен её код
                // function(loop) {
                //    console.log('Break label. Current index: ' + loop.init.i);
                //    loop.done(loop); // В результате прерывания цикла можно просто завершить выполнение цикла
                // }
                );
            }
            console.log(loop.init.i);
            loop.next(); // Обычный переход на следующий шаг цикла
          }
        , done: function (loop) {
            console.log('The End. Last index: ' + loop.init.i);
          }
    });
   
    // Пример 4 - Прерывание вложенных асинхронных циклов
   
    // Внешний цикл - начало
    async.forLoop({
          init: {i: 0}    
        , condition: function (outerLoop) {if (outerLoop.init.i < 5) {return true;} else {return false;}}  
        , iterate: function (outerLoop) {outerLoop.init.i = outerLoop.init.i + 1;}
        , body: function (outerLoop) {
            // Внутренний цикл - начало
            async.forLoop({
                  init: {j: 0}    
                , condition: function (innerLoop) {if (innerLoop.init.j < 5) {return true;} else {return false;}}  
                , iterate: function (innerLoop) {innerLoop.init.j = innerLoop.init.j + 1;}
                , body: function (innerLoop) {
                    if (innerLoop.init.j === 2) {
                       // Перепрыгивание на следующий шаг цикла
                        return innerLoop.continueLoop(
                        // Если в качестве метки label будет передана функция, то будет выполнен её код
                        // function(outerLoop){
                        //    console.log('Continue label. Current index: ' + innerLoop.init.j);
                        //    innerLoop.done(); // В результате перепрыгивания шага можно просто завершить выполнение внутреннего цикла
                        // }
                        );
                    }
                    if (innerLoop.init.j === 2) {
                        // Прерывание цикла
                        return innerLoop.breakLoop(
                        // Если в качестве метки label будет передана функция, то будет выполнен её код
                        // function(innerLoop){
                        //     console.log('Break label. Current index: ' + innerLoop.init.j);
                        //     outerLoop.done(outerLoop); // В результате прерывания цикла можно просто завершить выполнение внешнего цикла
                        // }
                        );
                    }
                    console.log(outerLoop.init.i + ' ' + innerLoop.init.j);
                    innerLoop.next(); // Обычный переход на следующий шаг внутреннего цикла
                  }
                , done: function (innerLoop) {
                    outerLoop.next(); // После завершения внутреннего цикла осуществляется переход на следующий шаг внешнего цикла
                  }
            });
            // Внутренний цикл - конец
          }
        , done: function (outerLoop) {
            console.log('All cycles done');
          }
    });
    // Внешний цикл - конец
   
    */
   
    // Асинхронный цикл forEach
    async.forEachInArray = function (params) {
   
        params = params || {}; // Параметры асинхронного цикла обработки массива
       
        params.array = params.array || []; // Исходный массив, предназначенный для асинхронной обработки
        params.process = params.process || function () {};  // Функция, выполняющая обработку элементов массива
        params.done = params.done || function () {}; // Функция, выполняющаяся после завершения обработки всех элементов массива
       
        params.processTimeLimit = (params.processTimeLimit !== undefined) ? params.processTimeLimit : 50; // Время в мс, отведенное для выполнения процесса обработки элементов массива
        // Если процесс обработки массива занимает больше времени, чем указано в processTimeLimit,
        // то следующий шаг обработки будет начат после некоторой задержки, указанной в params.timeout
        // Для ускорения выполнения процесса обработки элементов массива ограничение времени по умолчанию оставлено равным 50 мс
       
        params.timeout = (params.timeout !== undefined) ? params.timeout : 0; // Время задержки в мс перед началом следующего шага цикла по обработке элементов массива
        // В общем случае для выполнения маленьких задержек рекомендуется использовать значение не менее 25 мс,
        // потому что меньшие задержки оставляют слишком короткие интервалы времени для обновления пользовательского интерфейса.
        // Однако для ускорения выполнения цикла оставлена задержка 0 мс, если время задержки специально не указано
       
        // Мы создаем временный массив, в котором будем вести асинхронную обработку элементов, чтобы не повредить исходный массив,
        // который в данный момент может обрабатываться другой асинхронной функцией
        var tempArray = params.array.slice()
            , tempArrayLength = tempArray.length
            , currentTempArrayIndex = 0
            , breakProcess;
           
        if (tempArrayLength) {
            startIterationProcess();
        } else {
            params.done(tempArray);
        }
       
        function startIterationProcess () {
            var startTime = +new Date();
            do {
                breakProcess = params.process(tempArray[currentTempArrayIndex], currentTempArrayIndex, tempArray);
                if (breakProcess === 'break') {
                    tempArrayLength = 0;
                } else {
                    currentTempArrayIndex++;
                }
            } while (
                      currentTempArrayIndex < tempArrayLength
                && ((+new Date() - startTime) < params.processTimeLimit) // Установить таймаут, если цикл процесса обработки выполняется дольше установленного временного предела
            );
            if (currentTempArrayIndex < tempArrayLength) {
                if (params.timeout) { // Если время задержки в мс перед началом следующего шага цикла было установлено, то выполнить тело цикла с задержкой
                    setTimeout(startIterationProcess, params.timeout);
                } else { // Если время задержки в мс перед началом следующего шага цикла не было установлено или равно 0, то выполнить следующий шаг цикла немедленно
                    setImmediate(startIterationProcess);
                }
            } else {
                params.done(tempArray);
            }
        }
    };
   
    /* Пример кода асинхронного цикла forEach
   
    var arr = new Array(5000);
   
    async.forEachInArray({
          array: arr
        , process: function (item, index, array) {
            array[index] = item + '-' + index;
            if (index === 2000) {
                // Прервать выполнение итераций
                return 'break';
            }
          }
        , done: function (array) {
            arr = array;
            console.log('All done');
            console.log(arr);
        }
    });
   
    */
   
    // Асинхронная сортировка массива sort
   
    /* Описание алгоритма сортировки массивов
   
    Алгоритм сортировки массивов merge sort используют браузеры Firefox и Safari в своей реализации Array.prototype.sort()

    Алгоритм merge sort быстр в работе и легок в реализации.
    Он базируется на идее, что легче сравнить и слить два уже отсортированных списка,
    нежели разбираться с одним несортированным списком.
    Реализация алгоритма merge sort начинается с создания некоторого числа N одноэлементных массивов,
    где N - общее число элементов в оригинальном массиве, который требуется отсортировать.
    Затем эти одноэлементные массивы объединяются обратно в единый отсортированный массив.

    Сравнение и слияние двух уже отсортированных списков представляет собой довольной простой алгоритм.
    Представим, что у нас есть два списка: список A и список B.
    Мы начинаем с самого начала каждого списка и сравниваем два их значения.
    Меньшее из двух сравниваемых значений вставляется в итоговый массив.
    Пусть к примеру меньшим окажется значение из списка A, тогда это значение будет помещено в итоговый массив.
    Далее второе значение из списка A сравнивается с первым значением из списка B.
    Снова меньшее из двух значений вставляется в итоговый массив.
    Теперь для примера меньшим пусть будет число из списка B, тогда на следующем шаге будут сравниваться второй элемент из
    списка A и второй элемент из списка B.
    Код такого сравнения будет следующим:

    function merge (leftArray, rightArray) {
        var resultArray = []
            , leftArrayLength = leftArray.length
            , rightArrayLength = rightArray.length
            , indexLeft = 0,
            , indexRight = 0;
        while (
                  indexLeft < leftArrayLength
            && indexRight < rightArrayLength
        ) {
            if (leftArray[indexLeft] < rightArray[indexRight]) {
                result.push(leftArray[indexLeft]);
                indexLeft++;
            } else {
                result.push(rightArray[indexRight]);
                indexRight++;
            }
        }
        return resultArray.concat(leftArray.slice(indexLeft)).concat(rightArray.slice(indexRight));
    }

    В приведенной выше функции сравнивается и сливается два массива: левый и правый.
    Переменная indexLeft хранит в себе индекс сравниваемого элемента из списка leftArray,
    в то время как переменная indexRight хранит в себе индекс сравниваемого элемента из списка rightArray.
    Кажлый раз, когда значение из одного массива добавляется в итоговый массив,
    его соотвествующий индекс элемента увеличивается на единицу.
    Как только один из массивов закончится, то оставшиеся значания будут добавлены в конец итогового массива
    посредством операции concat().

    Функция merge() работает просто, но теперь необходимо объединить два отсортированных списка.
    Как было замечено ранее, это делается путем разделения массива на несколько одноэлементных массивов,
    которые затем объединяются в списки.
    Это можно легко сделать, применив рекурсию:

    function mergeSort (array) {
        var arrayLength = array.length;
        // Условие выхода из функции
        // Если число элементов в сортируемом массиве 0 или 1, то такой массив не требует сортировки
        if (arrayLength < 2) {
            return array;
        }
        var middle = Math.floor(arrayLength / 2) // определяем середину исходного массива
            , leftArray = items.slice(0, middle) // левая половина исходного массива
            , rightArray = items.slice(middle); // правая половина исходного массива
        return merge(mergeSort(leftArray), mergeSort(rightArray)); // Рекурсивно сравниваем и сливаем два массива,
        // периодически разделяя их пополам на более мелкие составляющие
    }

    Сперва стоит отметить, что если в сортируемом массивке 0 или 1 элемент, то такой массив не требует сортировки.
    Если массив содержит в себе 2 или более значений, то он разделяется пополам на левый и правый массивы.
    Каждый из этих массивов затем опять разделяется пополам, помещаясь обратно в функцию mergeSort(), после завершения которой
    результат помещается в функцию merge(), где происходит сравнение и слияние двух маленьких массивов с
    последующим помещением их в итоговый отсортированный массив.
    Таким образом сортируется сначала левая половина массива, а затем сортируется правая часть массива,
    после чего итоговые результаты сравниваются и объединяются.
    Благодаря рекурсии вы в конечно итоге доходите до точки, где два одноэлементных массива сравниваются и сливаются в итоговый массив.

    Имплементация алгоритма merge sort возвращает другой массив, отличающийся от того, который был передан для сортировки,
    то есть это не сортировка массива на месте. Если вам требуется отсортировать исходный массив на месте,
    то в этом случае вы можете очистить оригинальный массив и заполнить его отсортированным содержимым следующим образом:

    function mergeSort (array) {
        var arrayLength = array.length;
        // Условие выхода из функции
        // Если число элементов в сортируемом массиве 0 или 1, то такой массив не требует сортировки
        if (arrayLength < 2) {
            return array;
        }
        var middle = Math.floor(arrayLength / 2) // определяем середину исходного массива
            , leftArray = array.slice(0, middle) // левая половина исходного массива
            , rightArray = array.slice(middle) // правая половина исходного массива
            , params = merge(mergeSort(leftArray), mergeSort(rightArray));
        // Добавляем аргументы для замены всего, начиная с 0 и до последнего элемента в массиве
        params.unshift(0, arrayLength);
        array.splice.apply(array, params);
        return array;
    }

    Эта версия функции mergeSort() хранит результат сортировки в переменной params.
    Самый лучший способ заменить элементы в массиве - это использовать метод splice(), который принимает два и более аргументов.
    Первый аргумент - это индекс первого заменяемого значения, а второй аргумент - это число элементов, которые надо заменить.
    Каждый последующий аргумент - это значение, которое надо вставить на соотвествующщую позицию.
    Поскольку нет способа передать значения в метод splice(), то необходимо использовать метод apply(),
    в который передать первые два аргумента объединенные с отсортированным массивом.
    Таким образом 0 и array.length добавляются в начало массива, используя метод unshift() так,
    что apply() может быть использован совместно со splice().
    После этого исходный массив возвращается из функции.
   
    // Функция arrayMergeSort сортирует массив синхронно
    // По умолчанию сортировка производится по возрастанию
    // Параметр array - массив требующий сортировки
    // Необязательный параметр compareFunction - функция, задающая порядок сравнения элементов массива
    // Функция compareFunction должна возвращать:
    // 1 - если первый сравниваемый элемент массива больше второго сравниваемого элемента
    // -1 - если первый сравниваемый элемент массива меньше второго сравниваемого элемента
    // 0 - если сравниваемые элементы массива равны друг другу
    // Функция возвращает отсортированный массив
    function arrayMergeSort (array, compareFunction) {
        // для примера array = [5, 4, 6]
        var work = []
            , arrayLength = array.length // для примера arrayLength = 3
            , limit
            , i
            , j
            , k;
        // Для массивов с числом элементов 0 или 1 сортировка не требуется
        if (arrayLength < 2) {return array;}
        // Фомируем массив, состоящий из одноэлементных массивов, для примера work = [[5], [4], [6]]
        for (i = 0; i < arrayLength; i++){
            work.push([array[i]]);
        }
        work.push([]); // на случай, если в массиве нечетное число элементов work = [[5], [4], [6], []]
        // для примера изначально limit = 3, на каждом шаге цикла limit = Math.floor( (3 + 1) / 2 )), поскольку мы добавили пустой массив []
        for (limit = arrayLength; limit > 1; limit = Math.floor( (limit + 1) / 2 )) {
            for (j = 0, k = 0; k < limit; j++, k += 2) {
                work[j] = merge(work[k], work[k + 1], compareFunction);
            }
            work[j] = []; // на случай, если в массиве нечетное число элементов
        }
        return work[0];
    }
   
    */
   
    async.sort = function (params) {
   
        params = params || {}; // Параметры асинхронной сортировки массива
        params.array = params.array || []; // Исходный массив, предназначенный для асинхронной сортировки
        params.compare = params.compare || function (a, b) { // Функция, выполяющая сравнение элементов массива друг с другом
            if (a > b) {
                return 1;
            } else if (a < b) {
                return -1;
            } else {
                return 0;
            }
        };
        params.done = params.done || function () {}; // Функция, выполняющаяся после завершения сортировки массива
       
        // Для примера params.array = [5, 4, 6]
        var work = []
            , arrayLength = params.array.length; // Для примера arrayLength = 3

        // Для массивов с числом элементов 0 или 1 сортировка не требуется
        if (arrayLength < 2) {
            params.done(params.array); // Возвращение исходного массива
            return;
        }

        // Асинхронно фомируем массив, состоящий из одноэлементных массивов, для примера work = [[5], [4], [6]]
        async.forEachInArray({
              array: params.array
            , process: addElementsToWorkArray
            , done: startMergeProcess
        });
       
        // Добавляем элементы в рабочий массив work
        function addElementsToWorkArray (item) {
            work.push([item]);
        }
       
        // Начинаем асинхронный процесс сравнения и слияния элементов сортируемого массива
        function startMergeProcess () {
            work.push([]); // На случай, если в массиве нечетное число элементов work = [[5], [4], [6], []]
            // Для примера изначально limit = 3, на каждом шаге цикла limit = Math.floor( (3 + 1) / 2 )), поскольку мы добавили пустой массив []
            var limit = arrayLength;
            async.forLoop({
                  init: {limit: limit}
                , condition: function (outerLoop) {if (outerLoop.init.limit > 1) {return true;} else {return false;}}
                , iterate: function (outerLoop) {outerLoop.init.limit = Math.floor( (outerLoop.init.limit + 1) / 2 );}
                , body: function (outerLoop) {
                    async.forLoop({
                          init: {j: 0, k: 0}
                        , condition: function (innerLoop) {if (innerLoop.init.k < outerLoop.init.limit) {return true;} else {return false;}}
                        , iterate: function (innerLoop) {
                            innerLoop.init.j = innerLoop.init.j + 1;
                            innerLoop.init.k = innerLoop.init.k + 2;
                          }
                        , body: function (innerLoop) {
                            work[innerLoop.init.j] = merge(work[innerLoop.init.k], work[innerLoop.init.k + 1], params.compare); // Производим сравнение и слияние двух массивов
                            innerLoop.next(); // Переход на следующий шаг внутреннего цикла
                          }
                        , done: function (innerLoop) {
                            work[innerLoop.init.j] = []; // на случай, если в массиве нечетное число элементов
                            outerLoop.next(); // Переход на следующий шаг внешнего цикла
                          }
                    });
                  }
                , done: function () { // Завершение внешнего цикла
                    params.done(work[0]); // Возвращение отсортированного массива
                  }
            });
        }
       
        // Функция merge производит сравнение и слияние двух массивов по естесвенному порядку расположения чисел и строк в них
        // Параметр leftArray - первый массив для сравнение и слияния
        // Параметр rightArray - второй массив для сравнение и слияния
        // Необязательный параметр compareFunction - функция, задающая порядок сравнения элементов массива
        // Функция compareFunction должна возвращать:
        // 1 - если первый сравниваемый элемент массива больше второго сравниваемого элемента
        // -1 - если первый сравниваемый элемент массива меньше второго сравниваемого элемента
        // 0 - если сравниваемые элементы массива равны друг другу
        // Функция возвращает слитый массив
        function merge (leftArray, rightArray, compareFunction) {
            var resultArray = [];
            /* Функция compareFunction подставляется по умолчанию выше, однако этот код нужен на случай, если функция merge() будет вынесена отдельно
            if (compareFunction === undefined) {
                compareFunction =  function (a, b) { // Функция, выполяющая сравнение элементов массива друг с другом
                    if (a > b) {
                        return 1;
                    } else if (a < b) {
                        return -1;
                    } else {
                        return 0;
                    }
                };
            }
            */
            while (leftArray.length > 0 && rightArray.length > 0) {
                if (compareFunction(leftArray[0], rightArray[0]) === -1) {
                    resultArray.push(leftArray.shift());
                } else {
                    resultArray.push(rightArray.shift());
                }
            }
            resultArray = resultArray.concat(leftArray).concat(rightArray);
            // Убедимся, что оставшиеся массивы пусты
            leftArray.splice(0, leftArray.length);
            rightArray.splice(0, rightArray.length);
            return resultArray;
        }
       
    };

    /* Примеры асинхронной сортировки массива
   
    // Пример 1 - Асинхронная сортировка обычного массива
   
    var array = []
        , len = 999;
       
    for (;len--;) {
        array.push(len);
    }
   
    async.sort({
          array: array
        , done: function (result) {
            array = result;
            console.log(array);
          }
    });
   
    // Пример 2 - Асинхронная сортировка массива, содержащего объекты, по убыванию
   
    var array = [
          {n: 5}
        , {n: 4}
        , {n: 1}
        , {n: 3}
        , {n: 2}
    ];
   
    async.sort({
          array: array
        , compare: function (a, b) {
            if (a.n < b.n) {
                return 1;
            } else if (a.n > b.n) {
                return -1;
            } else if (a.n === b.n) {
                return 0;
            }
          }
        , done: function (result) {
            array = result;
            console.dir(array);
          }
    });
   
    */

    // Асинхронная перестановка элементов массива в обратном порядке reverse
    async.reverse = function (params) {
   
        params = params || {}; // Параметры функции асинхронной перестановки элементов массива в обратном порядке
       
        params.array = params.array || []; // Исходный массив, предназначенный для асинхронной перестановки элементов в обратном порядке
        params.done = params.done || function () {}; // Функция, выполняющаяся после завершения перестановки всех элементов массива
       
        params.timeout = (params.timeout !== undefined) ? params.timeout : 0; // Время задержки в мс перед началом следующего шага цикла перестановки элементов массива
        // В общем случае для выполнения маленьких задержек рекомендуется использовать значение не менее 25 мс,
        // потому что меньшие задержки оставляют слишком короткие интервалы времени для обновления пользовательского интерфейса.
        // Однако для ускорения выполнения цикла оставлена задержка 0 мс, если время задержки специально не указано
       
        // Мы создаем временный массив, в котором будем вести асинхронную перестановку элементов, чтобы не повредить исходный массив,
        // который в данный момент может обрабатываться другой асинхронной функцией
        var tempArray = params.array.slice()
            , tempArrayLength = tempArray.length
            , result = [];
        if (tempArrayLength > 2) {
            async.forLoop({
                  init: {i: tempArrayLength}
                , timeout: params.timeout
                , condition: function (loop) {if (loop.init.i > 0) {return true;} else {return false;}}
                , iterate: function (loop) {loop.init.i = loop.init.i - 1;}
                , body: function (loop) {
                    result.push(tempArray.pop());
                    loop.next(); // Переход на следующий шаг цикла
                  }
                , done: function () {
                    params.done(result);
                }
            });
        } else {
            params.done(tempArray);
        }
    };
   
    /* Пример асинхронной перестановки элементов массива в обратном порядке
   
    var array = []
        , len = 999;
       
    for (;len--;) {
        array.push(len);
    }
   
    async.reverse({
          array: array
        , done: function (result) {
            array = result;
            console.log(array);
           
            async.reverse({
                  array: array
                , timeout: 1
                , done: function (result) {
                    array = result;
                    console.log(array);
                  }
            });
           
          }
    });
   
    */
   
    // Выполнение асинхронных функций параллельно
    // Все результаты выполнения асинхронных функций собираются и передаются в виде массива в итоговую функцию doneFunction()
    // Данные из этого массива берутся согласно порядковому номеру записи асинхронной функции
    // Функция doneFunction() будет вызвана после того, как выполнение всех асинхронных функций будет завершено
    async.parallel = function (asyncFunctionsArray, doneFunction) {
        if (Object.prototype.toString.call(asyncFunctionsArray) !== '[object Array]') {
            throw new Error('First argument in async.parallel function must be array.');
        }
        if (doneFunction === undefined) {
            doneFunction = function () {};
        }
        var asyncFunctionsArrayLength = asyncFunctionsArray.length
            , counter = asyncFunctionsArrayLength // Счетчик хранит в себе число выполняющихся в данный момент асинхронных функций
            , results = [] // В этот общий массив будут сохраняться результаты выполнения всех асинхронных функций
            , i;
            if (asyncFunctionsArrayLength) {
                for (i = 0; i < asyncFunctionsArrayLength; i++) {
                   asyncFunctionsArray[i](makeCallback(i)); // Запустить функцию makeCallback() внутри каждой асинхронной функции, когда её выполнение завершится
                }
            } else {
                doneFunction(results);
            }
        // Функция makeCallback() будет вызываться в каждой асинхронной функции в случае её завершения
        // Она принимает порядковый индекс (номер) асинхронной функции для отслеживания того, какие результаты к какой функции относятся
        function makeCallback (index) {
            return function () { // Функция makeCallback() возвращает функцию, которая будет вызвана в каждой асинхронной функции в случае её завершения
                // Здесь мы используем объект функции arguments, поскольку некоторые асинхронные функции могут возвращать несколько значений сразу
                // Все итоговые результаты из асинхронной функции, переданные в качестве аргументов, добавляются в общий массив результатов в соотвествии с индексом,
                // который был присвоен данной функции
                results[index] = Array.prototype.slice.call(arguments);
                counter--; // При завершении работы асинхронной функции счетчик работающих в данный момент асинхронных функций будет уменьшаться на единицу
                // Далее производится проверка все ли асинхронные функции уже завершили свою работу
                // Если все асинхронные функции уже завершили работу, то счетчик будет равен 0
                // Следовательно пора запускать итоговую функцию, которая должна быть запущена после завершения всех асинхронных функций
                if (counter === 0) {
                    doneFunction(results);
                }
            };
        }
    };

    /* Примеры выполнения асинхронных функций параллельно
   
    // Пример 1 - Асинхронные функции без аргументов

    function a (callback) {
        setTimeout(function(){
            console.log('first');
            callback(1, 2);
        }, 3000);
    }

    function b (callback) {
        setTimeout(function(){
            console.log('second');
            callback(3, 4);
        }, 1000);
    }

    function c (callback) {
        setTimeout(function(){
            console.log('third');
            callback(5, 6);
        }, 2000);
    }

    function done (results) {
        console.log('done');
        console.log(results[0]);
        console.log(results[1]);
        console.log(results[2]);
    }

    async.parallel([a,b,c], done);
   
    // Пример 2 - Асинхронные функции с аргументами
   
    function a (x, y, callback) {
        setTimeout(function(){
            console.log('first');
            callback(x, y);
        }, 3000);
    }

    function b (x, y, callback) {
        setTimeout(function(){
            console.log('second');
            callback(x, y);
        }, 1000);
    }

    function c (x, y, callback) {
        setTimeout(function(){
            console.log('third');
            callback(x, y);
        }, 2000);
    }
   
    function done (message, results) {
        console.log(message);
        console.log(results[0]);
        console.log(results[1]);
        console.log(results[2]);
    }
   
    async.parallel([
          function (callback) { a(1, 2, callback); }
        , function (callback) { b(3, 4, callback); }
        , function (callback) { c(5, 6, callback); }
    ], function (results) { done('done', results); });

    */
       
    // Выполнение асинхронных функций по очереди
    // Все результаты выполнения асинхронных функций собираются и передаются в виде массива в итоговую функцию doneFunction()
    // Данные из этого массива берутся согласно порядковому номеру записи асинхронной функции
    // Функция doneFunction() будет вызвана после того, как все асинхронные функции будут выполнены
    async.queue = function (asyncFunctionsArray, doneFunction) {
        if (Object.prototype.toString.call(asyncFunctionsArray) !== '[object Array]') {
            throw new Error('First argument in async.queue function must be array.');
        }
        if (doneFunction === undefined) {
            doneFunction = function () {};
        }
        var asyncFunctionsArrayLength = asyncFunctionsArray.length
            , counter = asyncFunctionsArrayLength // Счетчик хранит в себе число оставшихся в очереди асинхронных функций
            , results = [] // В этот общий массив будут сохраняться результаты выполнения всех асинхронных функций
            , i = 0; // Индекс текущей выполняемой асинхронной функции
            if (asyncFunctionsArrayLength) {
                asyncFunctionsArray[i](makeCallback(i)); // Запустить функцию makeCallback() внутри первой асинхронной функции, когда её выполнение завершится
            } else {
                doneFunction(results);
            }
        // Функция makeCallback() будет вызываться в каждой асинхронной функции в случае её завершения
        // Она принимает порядковый индекс (номер) асинхронной функции для отслеживания того,
        // какие результаты к какой функции относятся и какую асинхронную функцию нужно запускать следующей
        function makeCallback (index) {
            return function () { // Функция makeCallback() возвращает функцию, которая будет вызвана в асинхронной функции в случае её завершения
                // Здесь мы используем объект функции arguments, поскольку некоторые асинхронные функции могут возвращать несколько значений сразу
                // Все итоговые результаты из асинхронной функции, переданные в качестве аргументов, добавляются в общий массив результатов в соотвествии с индексом,
                // который был присвоен данной функции
                results[index] = Array.prototype.slice.call(arguments);
                counter--; // При завершении работы асинхронной функции счетчик оставшихся в очереди асинхронных функций будет уменьшаться на единицу
                i++; // А индекс текущей выполняемой асинхронной функции будет увеличен на единицу, чтобы можно было вызвать следующую асинхронную функцию из очереди
                // Далее производится проверка все ли асинхронные функции уже завершили свою работу
                // Если все асинхронные функции уже завершили работу, то счетчик оставшихся в очереди асинхронных функций будет равен 0
                // Следовательно пора запускать итоговую функцию, которая должна быть запущена после завершения всех асинхронных функций
                if (counter === 0) {
                    doneFunction(results);
                } else { // В противном случае мы вызваем следующую асинхронную функцию из очереди
                    asyncFunctionsArray[i](makeCallback(i));
                }
            };
        }
    };
   
    /* Примеры выполнения асинхронных функций по очереди
   
    // Пример 1 - Асинхронные функции без аргументов

    function a (callback) {
        setTimeout(function(){
            console.log('first');
            callback(1, 2);
        }, 3000);
    }

    function b (callback) {
        setTimeout(function(){
            console.log('second');
            callback(3, 4);
        }, 1000);
    }

    function c (callback) {
        setTimeout(function(){
            console.log('third');
            callback(5, 6);
        }, 2000);
    }

    function done (results) {
        console.log('done');
        console.log(results[0]);
        console.log(results[1]);
        console.log(results[2]);
    }

    async.queue([a,b,c], done);
   
    // Пример 2 - Асинхронные функции с аргументами
   
    function a (x, y, callback) {
        setTimeout(function(){
            console.log('first');
            callback(x, y);
        }, 3000);
    }

    function b (x, y, callback) {
        setTimeout(function(){
            console.log('second');
            callback(x, y);
        }, 1000);
    }

    function c (x, y, callback) {
        setTimeout(function(){
            console.log('third');
            callback(x, y);
        }, 2000);
    }
   
    function done (message, results) {
        console.log(message);
        console.log(results[0]);
        console.log(results[1]);
        console.log(results[2]);
    }
   
    async.queue([
          function (callback) { a(1, 2, callback); }
        , function (callback) { b(3, 4, callback); }
        , function (callback) { c(5, 6, callback); }
    ], function (results) { done('done', results); });

    */

    // Выполнение асинхронных функций по очереди каскадом
    // Результат выполнения предыдущей асинхронной функции будет передаваться в следующую вызываемую асинхронную функцию
    // Результат выполнения последней асинхронной функции передается в итоговую функцию doneFunction()
    // Функция doneFunction() будет вызвана после того, как все асинхронные функции будут успешно выполнены
    // В случае ошибки хотя бы в одной из асинхронных функций из очереди, выполнение последующих асинхронных функций будет прервано и будет
    // вызвана функция errorFunction(), куда будут переданы успешно полученные из предыдущей асинзронной функции результаты
    // и индекс текущей асинхронной функции, в которой произошла ошибка
    async.cascade= function (asyncFunctionsArray, doneFunction, errorFunction) {
        if (Object.prototype.toString.call(asyncFunctionsArray) !== '[object Array]') {
            throw new Error('First argument in async.cascade function must be array.');
        }
        if (doneFunction === undefined) {
            doneFunction = function () {};
        }
        if (errorFunction === undefined) {
            errorFunction = function () {};
        }
        var asyncFunctionsArrayLength = asyncFunctionsArray.length
            , counter = asyncFunctionsArrayLength // Счетчик хранит в себе число оставшихся в очереди асинхронных функций
            , results = [] // В этот массив будут сохраняться результаты выполнения асинхронной функций
            , errors = [] // В этот массив будут сохраняться данные об ошибке в случае нейспешного завершения асинхронной функции
            , i = 0; // Индекс текущей выполняемой асинхронной функции
            if (asyncFunctionsArrayLength) {
                asyncFunctionsArray[i](makeSuccessCallback(), makeErrorCallback()); // Запустить функцию makeSuccessCallback() или функцию makeErrorCallback()
                // внутри первой асинхронной функции, когда её выполнение завершится
            } else {
                doneFunction(results);
            }
        // Функция makeSuccessCallback() будет вызываться в каждой асинхронной функции в случае её успешного завершения
        function makeSuccessCallback () {
            return function () { // Функция makeSuccessCallback() возвращает функцию, которая будет вызвана в асинхронной функции в случае её успешного завершения
                // Здесь мы используем объект функции arguments, поскольку некоторые асинхронные функции могут возвращать несколько значений сразу
                // Все итоговые результаты из асинхронной функции, переданные в качестве аргументов, добавляются в массив результатов
                results = Array.prototype.slice.call(arguments);
                counter--; // При завершении работы асинхронной функции счетчик оставшихся в очереди асинхронных функций будет уменьшаться на единицу
                i++; // А индекс текущей выполняемой асинхронной функции будет увеличен на единицу, чтобы можно было вызвать следующую асинхронную функцию из очереди
                // Далее производится проверка все ли асинхронные функции уже завершили свою работу
                // Если все асинхронные функции уже завершили работу, то счетчик оставшихся в очереди асинхронных функций будет равен 0
                // Следовательно пора запускать итоговую функцию, которая должна быть запущена после завершения всех асинхронных функций
                if (counter === 0) {
                    doneFunction(results);
                } else { // В противном случае мы вызваем следующую асинхронную функцию из очереди
                    if (asyncFunctionsArray.length) { // Проверяем длину массива на случай, если предыдущая асинхронная функций завершилась неуспешно
                        asyncFunctionsArray[i](makeSuccessCallback(), makeErrorCallback(), results);
                    }
                }
            };
        }
        // Функция makeErrorCallback() будет вызываться в каждой асинхронной функции в случае её неуспешного завершения
        function makeErrorCallback () {
            return function () { // Функция makeErrorCallback() возвращает функцию, которая будет вызвана в асинхронной функции в случае её неуспешного завершения
                asyncFunctionsArrayLength = [];
                counter = -1;
                errors = Array.prototype.slice.call(arguments);
                errorFunction(results, errors, i); // Мы прерываем цепочку вызова асинхронных функций и передаем управление в функцию обработки ошибок
            };
        }
    };
   
    /* Примеры выполнения асинхронных функций по очереди каскадом
   
    // Пример 1 - Успешное завершение всех асинхронных функций без аргументов
   
    function a (success, error) {
        setTimeout(function(){
            console.log('first');
            success(1, 2);
        }, 3000);
    }

    function b (success, error, results) {
        console.log('results from a');
        console.log(results);
        setTimeout(function(){
            console.log('second');
            success(3, 4);
        }, 1000);
    }

    function c (success, error, results) {
        console.log('results from b');
        console.log(results);
        setTimeout(function(){
            console.log('third');
            success(5, 6);
        }, 2000);
    }

    function done (results) {
        console.log('done');
        console.log('results from c');
        console.log(results);
    }
   
    function error (results, index) {
        console.log('error in function with index: ' + index);
        console.log('results');
        for (var i = 0, len = results.length; i < len; i++) {
            console.log(results[i]);
        }
    }

    async.cascade([a,b,c], done, error);
   
    // Пример 2 - Успешное завершение всех асинхронных функций с аргументами
   
    function a (success, error, num) {
        console.log(num);
        setTimeout(function(){
            console.log('first');
            success(1, 2);
        }, 3000);
    }

    function b (success, error, results, num) {
        console.log(num);
        console.log('results from a');
        console.log(results);
        setTimeout(function(){
            console.log('second');
            success(3, 4);
        }, 1000);
    }

    function c (success, error, results, num) {
        console.log(num);
        console.log('results from b');
        console.log(results);
        setTimeout(function(){
            console.log('third');
            success(5, 6);
        }, 2000);
    }

    function done (results, message) {
        console.log(message);
        console.log('done');
        console.log('results from c');
        console.log(results);
    }
   
    function error (results, index, message) {
        console.log(message);
        console.log('error in function with index: ' + index);
        console.log('results');
        for (var i = 0, len = results.length; i < len; i++) {
            console.log(results[i]);
        }
    }

    async.cascade(
          [
              function (success, error) { a(success, error, 1); }
            , function (success, error, results) { b(success, error, results, 2); }
            , function (success, error, results) { c(success, error, results, 3); }
          ]
        , function (results) { done(results, 'Well done'); }
        , function (results, index) { error(results, index, 'Super error'); }
    );
   
    // Пример 3 - Неуспешное завершение третьей асинхронной функции без аргументов
   
    function a (success, error) {
        var successTimeout = setTimeout(function(){
            console.log('first - success');
            success(1, 2, errorTimeout);
        }, 3000);
        var errorTimeout = setTimeout(function(){
            console.log('first - error');
            error(successTimeout);
        }, 5000);
    }

    function b (success, error, results) {
        clearTimeout(results[2]);
        console.log('results from a');
        console.log(results);
        var successTimeout = setTimeout(function(){
            console.log('second - success');
            success(3, 4, errorTimeout);
        }, 1000);
        var errorTimeout = setTimeout(function(){
            console.log('second - error');
            error(successTimeout);
        }, 5000);
    }

    function c (success, error, results) {
        clearTimeout(results[2]);
        console.log('results from b');
        console.log(results);
        var successTimeout = setTimeout(function(){
            console.log('third - success');
            success(5, 6, errorTimeout);
        }, 2000);
        var errorTimeout = setTimeout(function(){
            console.log('third - error');
            error(successTimeout);
        }, 10);
    }

    function done (results) {
        clearTimeout(results[2]);
        console.log('done');
        console.log('results from c');
        console.log(results);
    }
   
    function error (results, errors, index) {
        clearTimeout(errors[0]);
        console.log('error in function with index: ' + index);
        console.log('results');
        for (var i = 0, len = results.length; i < len; i++) {
            console.log(results[i]);
        }
        console.log('errors');
        for (i = 0, len = errors.length; i < len; i++) {
            console.log(errors[i]);
        }
    }

    async.cascade([a,b,c], done, error);
   
    // Пример 4 - Неуспешное завершение третьей асинхронной функции с аргументами
   
    function a (success, error, num) {
        console.log(num);
        var successTimeout = setTimeout(function(){
            console.log('first - success');
            success(1, 2, errorTimeout);
        }, 3000);
        var errorTimeout = setTimeout(function(){
            console.log('first - error');
            error(successTimeout);
        }, 5000);
    }

    function b (success, error, results, num) {
        console.log(num);
        clearTimeout(results[2]);
        console.log('results from a');
        console.log(results);
        var successTimeout = setTimeout(function(){
            console.log('second - success');
            success(3, 4, errorTimeout);
        }, 1000);
        var errorTimeout = setTimeout(function(){
            console.log('second - error');
            error(successTimeout);
        }, 5000);
    }

    function c (success, error, results, num) {
        console.log(num);
        clearTimeout(results[2]);
        console.log('results from b');
        console.log(results);
        var successTimeout = setTimeout(function(){
            console.log('third - success');
            success(5, 6, errorTimeout);
        }, 2000);
        var errorTimeout = setTimeout(function(){
            console.log('third - error');
            error(successTimeout);
        }, 10);
    }

    function done (results, message) {
        console.log(message);
        clearTimeout(results[2]);
        console.log('done');
        console.log('results from c');
        console.log(results);
    }
   
    function error (results, errors, index, message) {
        console.log(message);
        clearTimeout(errors[0]);
        console.log('error in function with index: ' + index);
        console.log('results');
        for (var i = 0, len = results.length; i < len; i++) {
            console.log(results[i]);
        }
        console.log('errors');
        for (i = 0, len = errors.length; i < len; i++) {
            console.log(errors[i]);
        }
    }

    async.cascade(
          [
              function (success, error) { a(success, error, 1); }
            , function (success, error, results) { b(success, error, results, 2); }
            , function (success, error, results) { c(success, error, results, 3); }
          ]
        , function (results) { done(results, 'Well done'); }
        , function (results, errors, index) { error(results, errors, index, 'Super error'); }
    );
   
    */

    // Выполнение асинхронных функций последовательно друг за другом

    // Обещание для выстраивания цепочки вызовов асинхронных функций
    async.Promise = function () {
        // Создается отдельно для каждого объекта класса Promise
        this.queue = []; // Массив очереди ожидающих вызова асинхронных функций
    };

    // Методы обещания
    async.Promise.prototype = {
   
          then: function(asyncFunctionsObject) { // then() создает цепочку вызовов асинхронных функций
            asyncFunctionsObject = asyncFunctionsObject || {}; // Набор функций, выполняющихся после завершения работы предыдущей асинхронной функции
            asyncFunctionsObject.success = asyncFunctionsObject.success || function () {}; // Функция, выполняющаяся в случае успешного завершения предыдущей асинхронной функции
            asyncFunctionsObject.error = asyncFunctionsObject.error || function () {}; // Функция, выполняющаяся в случае неуспешного завершения предыдущей асинхронной функции
            this.queue.push(asyncFunctionsObject); // Функции-коллбаки записываются в объект, который записывается в массив очереди ожидающих вызова асинхронных функций
            return this; // Возвращаем созданный ранее объект promise для передачи его в следующую функцию then()
          }
         
        , success: function(result) { // success() вызывается когда предыдущая асинхронная функция успешно завершается
            if (this.queue.length) {
                this.queue.shift().success(this, result); // В случае успешного завершения предыдущей асинхронной функции мы запускаем следующую асинхронную функцию из очереди
            }
          }

        , error: function(result) { // error() вызывается когда предыдущая асинхронная функция завершается неуспешно
            if (this.queue.length) {
                this.queue.shift().error(result); // В случае неуспешного завершения предыдущей асинхронной функции мы запускаем функцию, обрабатывающую ошибку
                this.queue = []; // и прерываем цепочку последующих вызовов, очищая очередь асинхронных функций
            }
          }
         
    };

    // Последовательный вызов асинхронных функций when -> then
    // В случае успешного завершения предыдущей асинхронной функции будет вызывать следующая асинхронная функция из цепочки
    // Результат выполнения предыдущей асинхронной функции будет передаваться в следующую вызываемую асинхронную функцию
    // В случае неуспешного завершения предыдущей асинхронной функции будет вызывать следующая функция для обработки ошибок из цепочки
    // после чего дальнейшее выполнени цепочки будет прервано
   
    async.when = function (asyncFunction) {
        var promise = new async.Promise();
        asyncFunction(promise);
        return promise;
    };
   
    /* Примеры выполнения асинхронных функций последовательно

    // Пример 1 - Успешное завершение всех асинхронных функций
   
    async.when(async1
    ).then({
          success: async2
        , error: error2
    }).then({
          success: async3
        , error: error3
    }).then({
          success: done
        , error: errorDone
    });

    function async1 (promise) {
        console.log('async1 -  Start');
        var successTimeout = setTimeout(function(){promise.success([errorTimeout, 'async1 - Success'])}, 3000);
        var errorTimeout = setTimeout(function(){promise.error([successTimeout, 'async1 - Error'])}, 4000);
    }
   
    function async2 (promise, result) {
        clearTimeout(result[0]);
        console.log(result[1]);
        console.log('async2 -  Start');
        var successTimeout = setTimeout(function(){promise.success([errorTimeout, 'async2 - Success'])}, 2000);
        var errorTimeout = setTimeout(function(){promise.error([successTimeout, 'async2 - Error'])}, 3000);
    }
   
    function async3 (promise, result) {
        clearTimeout(result[0]);
        console.log(result[1]);
        console.log('async3 -  Start');
        var successTimeout = setTimeout(function(){promise.success([errorTimeout, 'async3 - Success'])}, 1000);
        var errorTimeout = setTimeout(function(){promise.error([successTimeout, 'async3 - Error'])}, 2000);
    }
   
    function done (promise, result) {
        clearTimeout(result[0]);
        console.log(result[1]);
        console.log('All done');
    }
   
    function error2 (result) {
        clearTimeout(result[0]);
        console.log(result[1]);
    }
   
    function error3 (result) {
        clearTimeout(result[0]);
        console.log(result[1]);
    }
   
    function errorDone (result) {
        clearTimeout(result[0]);
        console.log(result[1]);
    }

    // Пример 2 - Неуспешное завершение второй асинхронной функцией - прерывание последующей цепочки вызовов
   
    async.when(async1
    ).then({
          success: async2
        , error: error2
    }).then({
          success: async3
        , error: error3
    }).then({
          success: done
        , error: errorDone
    });
   
    function async1 (promise) {
        console.log('async1 -  Start');
        var successTimeout = setTimeout(function(){promise.success([errorTimeout, 'async1 - Success'])}, 3000);
        var errorTimeout = setTimeout(function(){promise.error([successTimeout, 'async1 - Error'])}, 4000);
    }
   
    function async2 (promise, result) {
        clearTimeout(result[0]);
        console.log(result[1]);
        console.log('async2 -  Start');
        var successTimeout = setTimeout(function(){promise.success([errorTimeout, 'async2 - Success'])}, 2000);
        var errorTimeout = setTimeout(function(){promise.error([successTimeout, 'async2 - Error'])}, 10);
    }
   
    function async3 (promise, result) {
        clearTimeout(result[0]);
        console.log(result[1]);
        console.log('async3 -  Start');
        var successTimeout = setTimeout(function(){promise.success([errorTimeout, 'async3 - Success'])}, 1000);
        var errorTimeout = setTimeout(function(){promise.error([successTimeout, 'async3 - Error'])}, 2000);
    }
   
    function done (promise, result) {
        clearTimeout(result[0]);
        console.log(result[1]);
        console.log('All done');
    }
   
    function error2 (result) {
        clearTimeout(result[0]);
        console.log(result[1]);
    }
   
    function error3 (result) {
        clearTimeout(result[0]);
        console.log(result[1]);
    }
   
    function errorDone (result) {
        clearTimeout(result[0]);
        console.log(result[1]);
    }

    */
   
    return async; // Возвращаем объект, хранящий в себе перечень асинхронных функций из модуля

});