Promises. Не работает последовательность

287
20 декабря 2016, 23:33

Здравствуйте. Пишу код с использованием Обещаний для последовательного запуска функций. Только пару дней как разбираюсь с ними для улучшения кода (ибо раньше была ёлочка из коллбэков); Так вот. Есть функция анимации вывода текста на экран Animal():

function Animal(string) {
    var a = ''; //variable which will be entered character by character string
    var i= 0;//Letter counter
    var p = document.createElement('p');
    $('body').scrollTop($('body').append(p).height());
    return new Promise(function (resolve) {
        Anima();
        function Anima() {//Function of Animation   
            a+=string[i];
            i++;
            $('p').last().text(a);
           var timer = setTimeout(Anima, 100);
            if(i==string.length){
                clearTimeout(timer);
                resolve();
            };
        };
    });
};

И функция AnimalPause() для изображения пауз(выводится на экран строка из точек и сразу же удаляется):

function AnimalPause(string) {
var a = ''; //variable which will be entered character by character string
var i= 0;//Letter counter
var p = document.createElement('p');
$('body').scrollTop($('body').append(p).height());
return promise = new Promise(function (resolve) {
    Anima();
    function Anima() {//Function of Animation   
        a+=string[i];
        i++;
        $('p').last().text(a);
        var timer = setTimeout(Anima, 100);
        if(i==string.length){
            clearTimeout(timer);
            $('p').last().remove();
            resolve();
        };
    };
});
};

Все сделал, вроде бы. И, если выводить последовательно строки через .then() - всё работает. И, если выводить последовательно строки с использованием цикла - тоже всё работает:

Animal('..........')
        .then(() => Animal('---Hello! CONSOLE v 1.0.1 is working!---'))
        .then(() => Animal('To see all commands u can use type -help'))
        .then(() => Animal('DONE'))

или так:

var chain = Promise.resolve();
pause.forEach(function(txt){
                    chain = chain.then(() => AnimalPause(txt))});
var pause = [
    '...',
    '.....',
    '....',
    '.........'
];

Но если через .then() Последовательно выводить текст, потом задержку, потом - текст, рушится: (Задержка выводится после того, как выведется текст):

function Hello() {
var chain = Animal('..........')
        .then(() => Animal('---Hello! CONSOLE v 1.0.1 is working!---'))
        .then(() => Animal('To see all commands u can use type -help'))
        .then(function() {
                 return Pause(chain);
//               pause.forEach(function(txt){
//                   chain = chain.then(() => AnimalPause(txt))})
            })
        .then(() => Animal('DONE'))
};
Hello();
function Pause (chain) {
    return new Promise(function(resolve) {
        pause.forEach(function(txt){
                    chain = chain.then(() => AnimalPause(txt))});
        resolve();
    })
}

Почему? Ну..и как исправить? :)

По совету @Grundy в комментарии скидываю код для тестирования

var pause = [ 
  '...', 
  '.....', 
  '....', 
  '.........' 
]; 
 
function Animal(string) { 
  var a = ''; //variable which will be entered character by character string 
  var i = 0; //Letter counter 
  var p = document.createElement('p'); 
  $('body').scrollTop($('body').append(p).height()); 
  return new Promise(function(resolve) { 
    Anima(); 
 
    function Anima() { //Function of Animation    
      a += string[i]; 
      i++; 
      $('p').last().text(a); 
      var timer = setTimeout(Anima, 100); 
      if (i == string.length) { 
        clearTimeout(timer); 
        resolve(); 
      }; 
    }; 
  }); 
}; 
 
function AnimalPause(string) { 
  var a = ''; //variable which will be entered character by character string 
  var i = 0; //Letter counter 
  var p = document.createElement('p'); 
  $('body').scrollTop($('body').append(p).height()); 
  return promise = new Promise(function(resolve) { 
    Anima(); 
 
    function Anima() { //Function of Animation    
      a += string[i]; 
      i++; 
      $('p').last().text(a); 
      var timer = setTimeout(Anima, 100); 
      if (i == string.length) { 
        clearTimeout(timer); 
        $('p').last().remove(); 
        resolve(); 
      }; 
    }; 
  }); 
}; 
 
 
function Pause(chain) { 
  return new Promise(function(resolve) { 
    pause.forEach(function(txt) { 
      chain = chain.then(() => AnimalPause(txt)) 
    }); 
    resolve(); 
  }) 
}; 
 
function Hello() { 
  var chain = Animal('..........') 
    .then(() => Animal('---Hello! CONSOLE v 1.0.1 is working!---')) 
    .then(() => Animal('To see all commands u can use type -help')) 
    .then(function() { 
      return Pause(chain); //Вы заметите, что строка пауз выводится после вывода на экран строки "DONE". Должно быть наоборот 
    }) 
    .then(() => Animal('DONE')) 
 
}; 
Hello();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Answer 1

Если пройтись по коду, то можно отметить, что функция Animal отличается от AnimalPause только тем, что в последней в итоге удаляется добавленный элемент.

Исходя из этого можно передавать созданный p в resolve функции Animal и удалять его если надо. При этом AnimalPause выродится в следующее

function AnimalPause(string) {
  return Animal(string).then(p => p.remove());
};

Далее идет основная ошибка: функция Pause, которая добавляет продолжения для

var chain = Animal(...)

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

Вместо этого нужно вернуть Promise собранный на основе массива pause с помощью функции reduce

function Pause(pause) {
  return pause.reduce((chain, txt) => chain.then(() => AnimalPause(txt)), Promise.resolve());
};

В этом случае возвращенный Promise будет встроен в существующую цепочку и вызван в нужном порядке.

Пример в сборе:

var pause = [ 
  '...', 
  '.....', 
  '....', 
  '.........' 
]; 
 
function Animal(string) { 
  var a = ''; //variable which will be entered character by character string 
  var i = 0; //Letter counter 
  var p = document.createElement('p'); 
  $('body').scrollTop($('body').append(p).height()); 
  return new Promise(function(resolve) { 
    Anima(); 
 
    function Anima() { //Function of Animation    
      a += string[i]; 
      i++; 
      p.textContent = a; 
      var timer = setTimeout(Anima, 100); 
      if (i == string.length) { 
        clearTimeout(timer); 
        resolve(p); 
      }; 
    }; 
  }); 
}; 
 
function AnimalPause(string) { 
  return Animal(string).then(p => p.remove()); 
}; 
 
 
function Pause(pause) { 
  return pause.reduce((chain, txt) => chain.then(() => AnimalPause(txt)), Promise.resolve()); 
}; 
 
function Hello() { 
  var chain = Animal('..........') 
    .then(() => Animal('---Hello! CONSOLE v 1.0.1 is working!---')) 
    .then(() => Animal('To see all commands u can use type -help')) 
    .then(() => Pause(pause)) 
    .then(() => Animal('DONE')) 
 
}; 
Hello();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

READ ALSO
Загрузка больших файлов через Ajax

Загрузка больших файлов через Ajax

Всем приветПодскажите скрипт для загрузки больших файлов до 2 гб через ajax с возможность показа процента загрузки

457
Jquery animate from &hellip; to &hellip; (анимация от и до)

Jquery animate from … to … (анимация от и до)

Слабовато пока знаком с Jquery, сам найти ответ не сумел, возможно не там ищуНасколько я понял функция animate() принимает только конечные значения...

266
Из span в textarea

Из span в textarea

ЗдравствуйтеУ меня есть калькулятор

296
Сохранить данные в одну клетку массива в цикле

Сохранить данные в одну клетку массива в цикле

Имеется цикл процессовНапример такой

260