пятница, 21 июня 2013 г.

Пример promises

var fileP = select(downloadP("file.txt"), timeoutErrorP(2000));

fileP.then(function(file) {
    console.log("file: " + file);
}, function(error) {
    console.log("I/O error or timeout: " + error);
});

Promises

Асинхронные функции могут выполняться последовательно только, если их вызовы вкладывать друг в друга при возникновения события success.
Чтобы не загромождать код каскадным вложением вызовов йункций друг в друга были придуманы цепочки вызовов асинхроных функций.
При таком способе записи вызовов асинхронных функций следующий элемент цепочки будет выполнен только после того, как успешно будет выполнен предыдущий шаг.
Если на каком-либо шаге возникнет ошибка, то вся цепочка сразу прервется.


var results = searchTwitter(term).then(filterResults).then(addSomething);
displayResults(results);

В любой момент времени promises могут пребывать в одном из трех состояний:
unfulfilled - невыполнен
resolved - решен
rejected - отклонен

Метод then (when) всегда содержит обработчики для состояний resolved (success) и rejected (error) и возвращает следующее promise в цепочке, позволяя объединять асинхронные операции, в которых результат первой асинхронной операции передается во вторую асинхронную опереацию и так далее.
Callback-функция resolvedHandler срабатывает, когда promise переходит в состояне fulfilled - выполнено и передает результат расчетов далее.
Callback-функция rejectedHandler срабатывает, когда promise переходи в состояние fail - ошибка.

then(resolvedHandler, rejectedHandler);

Создадим небольшую библиотеку Promise для последовательного вызова асинхронных функций.

Сперва нам необъодимо сформировать объект, в котором будут хранится наши promise.

var Promise = function(){
    // здесь инициализирем promise
}

Далее нам надо добавить метод then, который позволит объединять асинхронные операции, базируясь на состоянии нашего promise.
Метод then в качестве аргументов принимает 2 функции onResolved (success) и onRejected (error).

Promise.prototype.then = function(onResolved, onRejected) {
    // вызываем функции обработчики onResolved или onRejected на основе текущего состояния promise
};

Также нам понадобится пара методов для оформления перехода между состояниями unfulfilled - невыполнен и resolved - выполне или rejected - отклонен.

Promise.prototype.resolve = function(value){
    // переход из состояния unfulfilled - невыполнен в состояние resolved - выполнен
}

Promise.prototype.reject = function(error){
    // переход из состояния unfulfilled - невыполнен в состояние rejected - отклонен
}

Добавим метод when, чтобы можно было обрабатывать асинхронные запросы по очереди.

Promise.when = function(){
    // обрабатываем каждый аргумент promises по очереди
}

Promise - это способ не записывать вызов асинхронного кода в виде пирамиды

step1 (function (value1) {
    step2 (value1, function (value2) {
        step3 (value2, function (value3) {
            step4 (value3, function (value4) {
                // Do something with value 4
            });
        });
    });
});

Замена пирамиды на Promises.

Q.fcall(step1).then(step2).then(step3).then(step4).then(function(value4){
    // Do something with value4
}, function (error) {
    // Handle any error from step1 through step4
}.done();

Монады - это инструменты, помогающие составлять композицию из функций, то есть создавать цепочки вызовов функций, в которых результат работы одной функции подставляется в другую функцию.

Promise - это контейнер, в котором будет сохраняться значение вызова асинхронной функции, а then выполняет работу по передаче этого значения из контейнера promise в следующую асинхронную функцию.

Приведем пример полного кода реализации Promises.

function Promise() {
  var self = this;
  this.pending = []; // Очередь ожидания обратных вызовов

  this.resolve = function(result) { // Вызывается когда что-то успешно завершается
  // Здесь мы будем в цикле проходить через функции обратных вызовов
    self.complete('resolve', result);
  },

  this.reject = function(result) { // Вызывается когда что-то ломается и происходит ошибка
    self.complete('reject', result);
  }
}

Promise.prototype = {
  // Создает цепочку вызовов асинхронных функций
  then: function(success, failure) {
    this.pending.push({ resolve: success, reject: failure });
    return this;
  },

  complete: function(type, result) {
    while (this.pending[0]) {
      this.pending.shift()[type](result);
    }
  }
};

// TESTS

// Пример асинхронной функции
function delay(ms) {
  var p = new Promise();
  setTimeout(p.resolve, ms);
  return p;
}

function timeout(duration, limit) {
  var p = new Promise();
  setTimeout(p.resolve, duration);
  setTimeout(p.reject, limit);
  return p;
}

delay(1000).then(function() {
  console.log('Delay complete');
  assert.ok('Delay completed');
});

timeout(10, 100).then(
  function() {
    console.log('Timeout 1 OK');
    assert.ok('10ms is under 100ms');
  },
  function() {
    assert.fail();
  }
);

timeout(100, 10).then(
  function() {
    assert.fail();
  },
  function() {
    console.log('Timeout 2 OK');
    assert.ok('100ms is over 10ms');
  }
);

================================================

Асинхронное программирование и Promises.

Удобство асинхронных операций в том, что выполнение асинхронных операций может вестись параллельно. Однако при этом возникает проблема когда требуется выполнить код, зависящий от выполнения нескольких асинхронных операций, которые могут завершиться порознь в неизвестные моменты времени.
Обычно эта проблема решается с помощью вызовов вложенных друг в друга функкций, когда следующая функция вызывается внутри предыдущей функции при успешном её выполнении.
Однако множество вложенных друг в друга функций в виде пирамиды делают код плохо читаемым и обработка ошибок значительно усложняется.

Пример.

// Данная функция выполняет асинхронный запрос на сервер для получения данных и в случае его успешного выполнения вызывает вложенную функцию.
// Если запрос прервался с ошибкой, то вызывается вложенная функция, которая обрабатывает эту ошибку.

function searchTwitter(word, success, error){
    var xhr
       , results
       , url = 'http://search.twitter.com/search.json?rpp=100&q=' + word;
 
    xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);
    xhr.onreadystatechange = function(){
        if (xhr.reasyState === 4) {
            if (xhr.status === 200) {
                results = JSON.parse(xhr.responseText);
                success(results);
            } else {
                alert('Error');
            }
        }
    };
    xhr.onerror =  function(e) {
        error(e);
    };
    xhr.send(null);
}

// Эта функция юудет вызвана при успешно выполнении всех асинхронных запросов.

function onSuccessConcatResults (results) {
    return concat(results);
}

// Эта функция будет вызвана при возникновении любой из ошибок во время выполнения любого асинхронного запроса.

function onErrorHandleError (e) {
    alert(e);
}

// Данная функция последовательно выполняет несколько асинхронных запросов и при успешном их завершении вызывает функцию onSuccessConcatResults, передавая в нее данные, полученные в результате выполнения всех асинхронных запросов.
// Вызов следующей асинхронной функции происходит только при успешно завершении работы предыдущей асинхронной функции.
// В случае возникновения ошибки на любом из этапов вызываетс функция onErrorHandleError.

function loadAsyncTweets () {

    var div = document.getElementById('container');

    searchTwitter('#IE10', function (data1){ // Выполняем первы асинхронный запрос. После успешного его завершения приступаем к выполнению следующего асинхронного запроса.

        searchTwitter('#IE9', function(data2){ // Выполняем следующий асинхронный запрос. После успешного его завершения выполняем функцию onSuccessConcatResults.

            var totalResults = onSuccessConcatResults (data1.results, data2.results); // Поскольку асинхронные функции  вложены друг в друга, то итоговые данные каждой из них попадают в итоговую функцию, которая должна быть вызвана в самом конце.

                 for (var i = 0; i < totalResults.length; i++) {
                     var el = document.createElement('li');
                          el.innerText = totalResults[i].text;
                          container.appendChild(el);
                 }

        }, onErrorHandleError); // При возникновении ошибки в любой из асинхронных функций вызывается функция onErrorHandleError.

    }, onErrorHandleError);

}

loadAsyncTweets();

Для решения проблемы данного пирамидальног кода была придумана абстракция обещаний или отложенного выполнения функций.

Обещания предоставляют результат выполнения длительного асинхронного процесса, не заботясь об успешном завершении этой операции.
Вместо того, чтобы блокировать выполнение следующей асинхронной функции и ждать завершения предыдущей асинхронной функции данный код возвращает объект, сразу представляющий результат обещания.
Например приложение отправляет запрос на сервер. Вместо того, чтобы ждать ответа от сервера оно может в это время выполнить ряд других действий, не требующих ответа от сервер.
Обещание имеет метод-функцию then(), которая регистрирует функции-колбаки, которые вызываются при изменении состояния выполнения асинхронного кода.

var results = searchTwitter(word).then(onSuccessConcatResults);
displayResults(results);

В любой момент времени асинхронная функция обещание может быть в одном из 3-х состояний:
- еще невыполнены,
- успешно выполнена,
- отвергнута (произошла ошибка).

Метод then(), принадлежащий объекту promiseObj, добавляет в массив внутри объекта функции-коллбаки resolved (success) и rejected (error).
then(successHandelerFunction, errorHandlerFunction);
Метод ther() возвращает ссылку на this - другой объект-обещание, создавая цепочку обещаний и позволяя выполнить несколько асинхронных операций, вызов которых зависит от успешного завершения предыдущей асинхронной операции в цепочке.

successHandelerFunction срабатывает когда объект-обещание успешно завершает выполнение асинхронной операции.
errorHandlerFunction - возникает, котгда при выполнении асинхронной операции возникает ошибка. В результате этого выполнение всей цепочки вызова асинхронных функций прерывается.

Пример организации асинхронной библиотеки обещаний.

var Promise = function () {} // Это функция-конструктор объектов обещаний.

Promise.prototype.then = function(success, error){} // Это метод объекта обещаний, который принимает функции-коллбаки, которые записываются в хранилище объекта и вызывается в случае успешного завершения асинхронной операции или возникновения ошибки.

Promise.prototype.goToSuccessState = function (value){} // Данный метод переводит объект обещания в состояний success.
Promise.prototype.goToErrorState = function (error) {} // Этот метод переводит объект обещания в состояние error.

Теперь перепишем наш пирамидальный пример с использованием метода then().

function searchTwitter(word, success, error){
    var xhr
       , results
       , url = 'http://search.twitter.com/search.json?rpp=100&q=' + word;
 
    xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);

    var promise = new Promise(); // Создадим объект-обещание.  

    xhr.onreadystatechange = function(){
        if (xhr.reasyState === 4) {
            if (xhr.status === 200) {
                results = JSON.parse(xhr.responseText);

                promise.goToSuccessState(results); // В случае успешного завершения асинхронной операции переведем объект обещания в состояние success.

            } else {
                 alert('Error');
            }
        }
    };

    xhr.onerror =  function(e) {
        promise.goToErrorState(); // В случае возникновения ошибки во твремя выполнения асинхронной операции переведем объект обещания в состояние error.
    };

    xhr.send(null);

    return promise; // Вернем объект обещание, чтобы можно было прикрепить к нему метод then().
}


function loadAsyncTweets () {

    var div = document.getElementById('container');

    searchTwitter('#IE10').then(function (data){

            var totalResults = onSuccessConcatResults (data);

                 for (var i = 0; i < totalResults.length; i++) {
                     var el = document.createElement('li');
                          el.innerText = totalResults[i].text;
                          container.appendChild(el);
                 }

    }, onErrorHandleError);

}

loadAsyncTweets();

Так мы выполнили одну асинхронную функцию, вызвав обработку результатов через функцию-коллбак, вызываемую после спешного её выполнения.

Для того, чтобы можно было управлять несколькими асинхронными функциями мы добавим в объект обещание метод when(), который будет записывать нащи объекты обещания в очередь и вызывать их по порядку записи.
Как только объект-обещание будет переведен в состояние succes или error, то соотвествующий будет вызван в методе then().
Метод when() - это просто операция ответвления-присоединения, которая ожидает завершения всех предыдущих операций, прежде чем продолжать дальнейшие действия.

Promise.prototype.when = function(){} // Этот метод записывает объекты обещани в очередь для последующих их вызовов по порядку их записи.

Теперь мы можем записывать несколько асинхронных функций, вызывая их по очереди.

var div = document.getElementById('container');

var promise1 = searchTwitter('#IE10'); // Запускаем выполнение первой асинхроной функции.
var promise2 = searchTwitter('#IE9'); // Запускаем параллельно выполнение второй ассинхронной функции.

// Как только обе асинхронные функции будут успешно выполнены запускаем выполнение нашего кода.

Promise.when(promise1, promise2).then(function(){

            var totalResults = onSuccessConcatResults (data1.results, data2.results);

                 for (var i = 0; i < totalResults.length; i++) {
                     var el = document.createElement('li');
                          el.innerText = totalResults[i].text;
                          container.appendChild(el);
                 }

}, onErrorHandleError);


// ПРИМЕР БИБЛИОТЕКИ PROMISE

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

function Promise() {
  this.queue = []; // Очередь ожидания обратных вызовов

  var self = this;

  this.success = function(result) { // Вызывается когда что-то успешно завершается
    self.complete('successFunction', result);
  },

  this.error = function(result) { // Вызывается когда что-то ломается и происходит ошибка
    self.complete('errorFunction', result);
  }
}

Promise.prototype = {
  // Создает цепочку вызовов асинхронных функций
  then: function(successFunction, errorFunction) { // Функции-коллбаки записываются в объект который записывается в массив очереди вызовов асинхронных функций.
    this.queue.push({ successFunction: successFunction, errorFunction: errorFunction });
    return this;
  },

  complete: function(type, result) {
    while (this.queue[0]) { // Вызов первого объекта с функциями-коллбаками, до тех пор пока число объектов в очереди не кончится.
      this.queue.shift()[type](result); // десьь записано queue.shift('successFunction')(result);
    }
  }
};


// ТЕСТЫ

// Пример асинхронной функции 1

function AsyncDelayTime(ms) {
  var p = new Promise(); // Создаем объект обещание
  setTimeout(p.success, ms); // Вызываем событие success через некотрое количество секунд.
  return p; // Возвращаем объект обещание для создания цепочки вызовов асинхронных функций.
}

AsyncDelayTime(1000).then(function() {
  console.log('Delay complete');
});

// Пример асинхронной функции 2

function AsyncTimeout(duration, limit) {
  var p = new Promise(); // Создаем объект обещание
  setTimeout(p.success, duration); // Вызываем событие success через некотрое количество секунд.
  setTimeout(p.error, limit); // Вызываем событие error через некотрое количество секунд.
  return p; // Возвращаем объект обещание для создания цепочки вызовов асинхронных функций.
}

AsyncTimeout(10, 100).then(
  function() {
    console.log('Timeout 1 OK');
  },
  function() {
    console.log('Timeout Fail');
  }
);

AsyncTimeout(100, 10).then(
  function() {
    console.log('Timeout 1 OK');
  },
  function() {
    console.log('Timeout Fail');
  }
);

================================================

// Небольшая библиотека When - Then позволяет выполнить произвольный код только после успешного выполнения всех перечисленных асинхронных операций.

// Представим, что вам надо выполнить параллельно несколько ассинхронных запросов на сервер, но вы хотите обработать результаты ответов только после того, как все асинхронные запросы завершаться успешно.

// When - Then позволяет передавать асинхронные функции в качестве аргументов в метод when(), вызывая по цепочке метод then() после того, как все асинхронные функции будут выполнены успешно.


// Пример

var data = {};

function collectData(key, value) {
    data[key] = value;
}

when(
    function(){
        collectData('tm0', 2);
    },
    function(){
        setTimeout(function(){
            collectData('tm1', 1000);
        }, 1000);
    },
    function(){
        setTimeout(function(){
            collectData('tm2', 100);
        }, 100);
    },
    function(){
        setTimeout(function(){
            collectData('tm3', 2000);
        }, 2000);
    }
)
.then(function){
    console.log(data);

    // returns an object
    //{
    //    tm0: 2,
    //    tm1: 1000,
    //    tm2: 100,
    //    tm3: 2000,
    //}
})

// Исходный код библиотеки When - Then

var when = function() {
    if ( !(this instanceof when) ) {return new when(argument);} // Вернуть новый объект или уже созданный объект.

    var self = this; // Закэшировать this для использования его в функции ниже и для удобства чтения кода.

    self.pending = Array.prototype.slice.call(arguments[0]); // Конвертировать аргументы, переданные функции в массив, содержащий очередь ассинхронных функций.
    self.pending_length = self.pending.length; // Закэшировать длину массива для использования в дальнейшем.
    self.results = {length: 0}; // Создаем контейнер для хранения результатов выполнения ассинхронных функций.

    (function(){ // Определим функция pass() внутри контекста для того, чтобы внешняя переменная self(this) была доступна, когда функция pass() выполнится внутри пользовательской асинхронной функции.
        self.pass = function(){

            // self.results.push(arguments); // Добавить результаты выполнения асинхронной функции в массив результатов.
            self.results[arguments[0]] = arguments[1];
            self.results.length++;

            if (self.results.length === self.pending_length) { // Если все асинхронные функции выполнены, то передать результаты в метод .then(), который был переопределен завершающей функцией пользователя.
                self.then.call(self, self.results);
            }
        }
    })();
}

when.prototype = {
    then: function () {
        this.then = arguments[0]; // переопределение метода .then() функцией пользователя, которая будет выполнена в самом конце. Также убеждаемся, что this() может бытьвызван только 1 раз при использовании функции when().
        while(this.pending[0]){
            this.pending.shift().call(this, this.pass); // Выполнить все асинхронные функции, которые пользователь передал в метод when().
        }
    }
}

================================================

Лучшее объяснение Promises - последовательное и параллельное выполнения асинхронных функций.

Последовательное выполнение асинхронных функций.

Асинхронные функции могут выполняться последовательно только, если их вызовы вкладывать друг в друга при возникновении события success.
Чтобы не загромождать код каскадным вложением вызовов функций друг в друга были придуманы цепочки вызовов асинхронных функций.
При таком способе записи вызовов кода функций следующий элемент цепочки будет выполнен только после того, как успешно будет выполнен предыдущий шаг.
Если на каком-либо шаге возникнет ошибка, то вся цепочка сразу прервется.

Простое объяснение механизма работы Promise.

Берем любую асинхронную функцию.
Внутри этой функции создаем объект Promise. Объект Promise имеет методы then, success и error.

Наша асинхронная функция будет возвращать (return) этот объект Promise в конце своего кода для того, чтобы можно было вызвать метод объекта then().

В методе then() описываются 2 функции: первая должна будет выполнить колбак-функцию в случае возникновения события success в асинхронной функции, а вторая колбак-функция должна будет выполниться при возникновении события error в асинхронной функции.

В асинхронной функции при возникновении события success вызывается метод объекта Promise.success(data), куда подставляется результат успешного выполнения асинхронного запроса. Вызов этого метода ссылается на вызов функции-колбака, описанной первой внутри метода then().

То есть вызов Promise.success(data) приводит к вызову того кода, что описан в методе then(function(data){alert(data);}) первым.

Аналогичным образом при возникновении события error в асинхронной функции вызывается метод объекта Promise.error(error_data), куда подставляются данные в случае возникновения ошибки во время выполнения асинхронного запроса. Вызов этого метода ссылается на вызов функции-колбака, описанного также в методе then, но второй.

То есть вызов Promise.error(error_data) приводит к вызову того кода, что описан в методе then вторым then(function(data){alert(data);}, function(error_data){alert(error_data);})

Таким образом вызов функций success и error описывается в коде асинхронной функции, а код самих функции описывается внутри метода then(). То есть таким образом мы переходим от пирамидальной записи кода к линейной.
Смысл кода не меняется, мы все так же вызываем функции при срабатывании событий success и error, меняется только формат записи кода.

var Promise = function () {
    this.code_functions = [];
    this.success = funciton (data) {this.code_functions[0](data)};
    this.error     = function (error) {this.code_functions[1](data)};
    this.then     = function (success_code, error_code) {
                                                this.code_functions.push(success_code).push(error_code)
    };
}

function Async() {
   var xhr = new XMLHttpRequest();

   // ... some async code ...

   var Promise = new Promise();

   xhr.onload =  function () {
       var data = 1;
       Promise.success(data);
   };

   xhr.error = function () {
       var error = 2;
       Promise.error(error);
   };

   xhr.send(null);

  return Promise;
}

Async().then(function (data){alert(data);}, function (error){alert(error);});

Код функций success() и error() записывается внутри метода then() объекта Promise. Таким образом этот код попадает внутрь объекта Promise, который находится внутри асинхронной функции Async().
За счет того, что теперь код функций находится внутри объекта Promise, он может вызываться внутри функции Async при срабатывании событий success и error, где производится подстановка аргументов в код этих функций и его выполнение.

Таким образом осуществляется последовательный вызов асинхронных функций.

Для последовательного выполнения нескольких асинхронных функций, в которых результат выполнения последующей функции зависит от выполнения предыдущей асинхронной функции можно написать цепочку вызовов, состоящих из методов then().

Async().then(Async2(), error).then(Async3(), error);

Вызов Async2, Async3 и далее происходит при возникновении события success в предыдущей функции. До возникновения момента этого события код следующей асинхронной функции не срабатывает. Он только хранится внутри объекта Promise.
И конечно, в следующих асинхронных функциях Async2 и Async3 необходимо вручную создавать новые объекты Promise для того, чтобы для них можно было выполнить их частный метод then().

Если необходимо дождаться успешного завершения нескольких асинхронных функций перед запуском кода из метода then(), то в этом случае на помощь приходит метод when() объекта Promise.

Параллельное выполнение асинхронных функций.

В функцию when() подставляется очередь из асинхронных функций, представленных своим кодом.
Код этих функций записывается в массив внутри when().
Результаты выполнения асинхронных функций записываются в массив results внутри функции when().
При успешном завершении работы асинхронных функций каждая из них будет записывать свой результат в массив results.
После того, как число успешных результатов сравняется с числом функций в массиве внутри when() будет вызван итоговый метод then(), в котором описан код, который должен быть выполнен только при успешном завершении всех асинхронных функций перечисленных в функции when().

Для отслеживания завершения работы асинхронных функций, работающих параллельно, используется отдельная переменная.

В данному примере асинхронные функции не принимают никаких аргументов, но вы можете расширить код для того, чтобы асинхронные функции могли принимать аргументы и иметь колбак-функции, записывающие результаты их выполнения с последуюшей передачей в завершающую функцию.

function parallel (async_funcs, done_callback) {

  var counter = async_funcs.length; // Счетчик хранит в себе число работающих в данный момент асинхронных функций.

  function callback () { // Данная функция будет вызываться в каждой асинхронной функции в случае успешного её завершения.
    counter--;              // При успешном завершении работы асинхронной функции счетчик работающих в данный момент асинхронных функций будет уменьшаться на единицу.
    if (counter === 0) { // Производится проверка все ли асинхронные функции уже успешно завершили свою работу. Если все асинхронные функции уже завершили работу, то счетчик будет равен 0. Следовательно пора запускать итоговую функцию, которая должна быть запущена после успешного завершения всех асинхронных функций.
      done_callback();
    }
  }

  for (var i=0; i < async_funcs.length; i++) {

    async_funcs[i](callback); // Запустить функцию callback() внутри каждой асинхронной функции, когда результат её выполнения завершится успешно.
                                       // Так каждая асинхронная функция при своем завершении уменьшает счетчик на единицу.
                                       // Как только все асинхронные функции завершатся счетчик станет равным 0 и будет выполнена завершающая функция.

  }

}

function A (counterCallback) {xhr.onload = function(){counterCallback();}}
function B (counterCallback) {xhr.onload = function(){counterCallback();}}
function C (counterCallback) {xhr.onload = function(){counterCallback();}}

function D () {alert('All done');}

parallel([A, B, C], D);

Дополнительно в функцию parallel() можно передавать асинхронные функции в любом виде.

parallel([
    function(counterCallback){ A(1, 2, counterCallback); },
    function(counterCallback){ B(1, counterCallback); },
    function(counterCallback){ C(1, 2, counterCallback); }
], D);

Так мы можем передавать асинхронные функции с аргументами, используя функции-замыкания. Останется только собрать итоговые результаты выполнения функций A, B, С и передать их в функцию D.

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

function parallel (async_funcs, done_callback) {

  var counter = async_funcs.length; // Счетчик хранит в себе число работающих в данный момент асинхронных функций.

  var all_results = []; // В этот общий массив будут сохраняться результаты выполнения всех асинхронных функций.



  function makeCallback (index) { // Данная функция будет вызываться в каждой асинхронной функции в случае успешного её завершения. Она принимает порядковый индекс (номер) асинхронной функции для отслеживания того, какие результаты к какой функции относятся.

    return function () { // Эта функция возвращает функцию, которая будет вызвана в каждой асинхронной функции в случае успешного её завершения.

      counter--; // При успешном завершении работы асинхронной функции счетчик работающих в данный момент асинхронных функций будет уменьшаться на единицу.

      var results = []; // В данном массиве конкретная успешно завершенная асинхронная функция будет сохранять итоговые результаты своей работы.

      // Мы используем объект функции arguments, поскольку в Node.js некоторые функции могут возвращать несколько значений сразу.

      for (var i = 0; i < arguments.length; i++) {
        results.push(arguments[i]); // Все результаты из асинхронной функции добавляются в массив результатов.
      }

      all_results[index] = results; // Результаты выполнения асинхронной функции добавляются в общий массив результатов в соотвествии с индексом, который был присвоен данной функции.


      if (counter == 0) {
            done_callback(all_results); // Производится проверка все ли асинхронные функции уже успешно завершили свою работу. Если все асинхронные функции уже завершили работу, то счетчик будет равен 0. Следовательно пора запускать итоговую функцию, которая должна быть запущена после успешного завершения всех асинхронных функций.
      }


    }
  }


  for (var i=0; i<async_funcs.length; i++) {
    async_calls[i](makeCallback(i)); // Запустить функцию makeCallback() внутри каждой асинхронной функции, когда результат её выполнения завершится успешно.
  }


}

В отличии от первого варианта этой функции итоговые резлультаты выполнения всех асинхронных функции передаются в качестве массива в итоговую функцию. Данные из массива берутся согласно порядковому номеру записи функции.

Пример использования такой функции в Node.js

// Прочитать 3 файла параллельно и в конце обработать данные из них:

function A (c){ fs.readFile('file1',c) };
function B (c){ fs.readFile('file2',c) };
function C (c){ fs.readFile('file3',c) };

function D (result) {
  file1data = result[0][1];
  file2data = result[1][1];
  file3data = result[2][1];

  // обработать итоговые данные здесь
}

fork([A,B,C], D);

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

================================================

Используйте setTimeout() вместо setInterval() для последовательного выполнения в цикле асинхронных операций.

Представьте, что вам нужно выполнять функцию, которая периодически осуществляет открытие и считывание данных из log-файла.

setInterval(function(){
    myAsyncParseFile(function(){
        console.log('parsing finished');
    });
}, 1000);

Однако нужно быть уверенным, что выполнении нескольких таких функций не произойдет в одно и тоже время.
Но этого нельзя гарантировать, если вы используете функцию setInterval().

Для того, чтобы быть уверенным, что выполнение следующей функции не совпадет с выполнением предыдущей используется следующий код с функцией setTimeout внутри замыкания:

(function shedule (){
    setTimeout(function(){
        myAsyncParseFile(function(){
            console.log('parsing finished');
            shedule();
        });
    }, 1000);
})();

Здесь мы декларируем функцию-замыкание shedule(), которую сразу же запускаем.
Функция shedule запускает внутри себя функцию setTimeout().
Через 1 секунду функция setTimeout выполняет асинхронную функцию myAsyncParseFile.
В асинхронную функцию myAsyncParseFile передается колбак-функция, которая будет выполнена в конце успешног завершения работы асинхронной функции.
В заключении своего выполнения колбак-функция вызывает внешнюю функцию shedule(), которая запускает опять всю цепочку выполнения через 1 секунду.
Таким образом данный код имитирует поведение функции setInterval(), но при этом гарантирует, что выполнение асинхронного кода не наложится и не произойдет одновременно.

По-другому этот код можно записать так:

function shedule () {
    setTimeout(function(){
        myAsyncParseFile(function(){
            console.log('parsing finished');
            shedule(); // Рекурсивный вызов функции самой себя.
        });
    }, 1000);
}

shedule ();

Комментариев нет:

Отправить комментарий