Цикл с нативными промисами;

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

var i = 0;

//creates sample resolver
function payloadGenerator(){
    return function(resolve) {
        setTimeout(function(){
            i++;
            resolve();
        }, 300)
    }
}

// creates resolver that fulfills the promise if condition is false, otherwise rejects the promise.
// Used only for routing purpose
function controller(condition){
    return function(resolve, reject) {
        console.log('i =', i);
        condition ? reject('fin') : resolve();
    }
}

// creates resolver that ties payload and controller together
// When controller rejects its promise, main fulfills its thus exiting the loop
function main(){
    return function(resolve, reject) {
        return new Promise(payloadGenerator())
            .then(function(){
                return new Promise(controller(i>6))
            })
            .then(main(),function (err) {
                console.log(err);
                resolve(err)
            })
            .catch(function (err) {
                console.log(err , 'caught');
                resolve(err)
            })
    }
}


new Promise(main())
    .catch(function(err){
        console.log('caught', err);
    })
    .then(function(){
        console.log('exit');
        process.exit()
    });

Теперь вывод:

/usr/local/bin/iojs test.js
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
fin
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
caught [TypeError: undefined is not a function]
exit

Process finished with exit code 0

Хорошая часть: она достигает конца.

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


person amdc    schedule 25.01.2015    source источник
comment
Независимо от того, какие библиотеки используются, то, как вы используете промисы, действительно странно. Какова ваша конечная цель здесь? Вы хотите реализовать время с промисами?   -  person Benjamin Gruenbaum    schedule 25.01.2015
comment
.then(main(),function (err) {. Когда main() там делает?   -  person Gil Elad    schedule 25.01.2015
comment
Вы забыли сказать нам, что этот код должен делать.   -  person JLRishe    schedule 25.01.2015
comment
@BenjaminGruenbaum Да, я знаю. Я новичок в обещаниях и пытаюсь понять, как они работают. Да, это цикл while; JLRishe: Он должен считать до 7 и не выдавать никаких ошибок   -  person amdc    schedule 25.01.2015


Ответы (5)


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

С .thenReturn немного лучше, но да:

function readFile(index) {
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Read file number " + (index +1));
            resolve();
        }, 500);
    });
}

// The loop initialization
Promise.resolve(0).then(function loop(i) {
    // The loop check
    if (i < len) {              // The post iteration increment
        return readFile(i).thenReturn(i + 1).then(loop);
    }
}).then(function() {
    console.log("done");
}).catch(function(e) {
    console.log("error", e);
});

См. это в jsfiddle http://jsfiddle.net/fd1wc1ra/.

Это почти точно эквивалентно:

try {
    for (var i = 0; i < len; ++i) {
        readFile(i);
    }
    console.log("done");
} catch (e) {
    console.log("error", e);
}

Если вы хотите сделать вложенные циклы, это точно так же:

http://jsfiddle.net/fd1wc1ra/1/

function printItem(item) {
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Item " + item);
            resolve();
        }, 500);
    });
}

var mdArray = [[1,2], [3,4], [5,6]];
Promise.resolve(0).then(function loop(i) {
    if (i < mdArray.length) {
        var array = mdArray[i];
        return Promise.resolve(0).then(function innerLoop(j) {
            if (j < array.length) {
                var item = array[j];
                return printItem(item).thenReturn(j + 1).then(innerLoop);
            }
        }).thenReturn(i + 1).then(loop);
    }
}).then(function() {
    console.log("done");
}).catch(function(e) {
    console.log("error", e);
});
person Esailija    schedule 25.01.2015
comment
Великолепно, спасибо! Хороший трюк с thenReturn. Одно замечание: ваш код здесь отличается от кода на jsfiddle, я сначала запутался. Версия jsfiddle работала для меня, так как реализация обещания iojs не имеет thenReturn. - person amdc; 25.01.2015
comment
@amdc thenReturn принадлежит bluebird, но может быть реализован на собственных промисах, как показано в файле jsifddle. - person Esailija; 25.01.2015
comment
Работает очень хорошо! Я хочу иметь возможность делать 5 циклов одновременно, и когда один из них будет выполнен, продолжайте с 6-м и так далее, пока не будет выполнен массив. Какие-либо предложения? Оформить заказ stackoverflow.com/q/36664272/1760313 - person Tom; 16.04.2016

Если все, что вы пытаетесь сделать, это сосчитать до 7 с обещаниями, то это сделает это:

function f(p, i) {
  return p.then(function() {
    return new Promise(function(r) { return setTimeout(r, 300); });
  })
  .then(function() { console.log(i); });
}

var p = Promise.resolve();
for (var i = 0; i < 8; i++) {
  p = f(p, i);
}
p.then(function() { console.log('fin'); })
 .catch(function(e) { console.log(e.message); });

Зацикливаться с промисами сложно, потому что почти невозможно не попасть в замыкания JavaScript в петлевая ловушка, но это выполнимо. Вышеупомянутое работает, потому что оно выталкивает все использование .then() в подфункцию f цикла (т.е. вне цикла).

Более безопасное решение, которое я использую, состоит в том, чтобы вообще отказаться от циклов и искать такие шаблоны, как forEach и reduce, когда я могу, потому что они эффективно навязывают вам подфункцию:

[0,1,2,3,4,5,6,7].reduce(f, Promise.resolve())
.then(function() { console.log('fin'); })
.catch(function(e) { console.log(e.message); });

здесь f — та же функция, что и выше. Попробуйте.

Обновление: В ES6 вы также можете использовать for (let i = 0; i < 8; i++), чтобы избежать ловушки "замыкания в цикле" без помещения кода в подфункцию f.

PS: ошибка в вашем примере .then(main(), - должно быть .then(function() { return new Promise(main()); },, но на самом деле, я думаю, вы неправильно используете шаблон. main() должен вернуть обещание, а не быть обернутым одним.

person jib    schedule 26.01.2015
comment
Это цикл for - я думаю, что OP собирался использовать while, который не будет работать с последовательной цепочкой, поскольку вы не будете знать, сколько цепочек вам нужно заранее. - person Benjamin Gruenbaum; 26.01.2015
comment
Может быть, ОП имел в виду, что условие зависело от выполнения предыдущих шагов? Если это так, то да, рекурсия будет необходима. В любом случае, использование цикла while вместо цикла for не решает эту проблему и не означает, что это так. Я изменил цикл for на цикл while, чтобы показать это. ОП сказал в комментарии, что пытался сосчитать до 7 обещаниями, и попросил кого-нибудь указать на его ошибку, что, надеюсь, я и сделал. - person jib; 26.01.2015
comment
Конечно, изменение цикла for на другую конструкцию синхронного цикла не поможет - я имел в виду, что концептуально количество итераций зависит от предыдущего состояния. - person Benjamin Gruenbaum; 26.01.2015
comment
@BenjaminGruenbaum for петли тоже в порядке. всегда хорошо знать, как сделать это по-разному, спасибо за вклад. - person amdc; 26.01.2015

Попробуйте регистрировать err.stack вместо err при обнаружении ошибок промисов.

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

Изменить: это проблема .then(main(),function (err) {. Вызов main сам по себе приведет к тому, что resolve и reject внутри анонимной функции будут неопределенными. Насколько я понял, main можно вызывать только в качестве аргумента конструктора Promise.

person tydotg    schedule 25.01.2015

У меня была аналогичная потребность, и я попробовал принятый ответ, но у меня возникла проблема с порядком операций. Promise.all это решение.

function work(context) {
  return new Promise((resolve, reject) => {
    operation(context)
      .then(result => resolve(result)
      .catch(err => reject(err));
  });
}

Promise
  .all(arrayOfContext.map(context => work(context)))
  .then(results => console.log(results))
  .catch(err => console.error(err));
person jonpeck    schedule 26.05.2017

Я также искал различные решения и не смог найти то, которое меня удовлетворило бы, поэтому в итоге я создал свое собственное. Вот, вдруг кому пригодится:

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

В моем случае вспомогательная функция просто так:

function promiseChain(chain) {
    let output = new Promise((resolve, reject) => { resolve(); });
    for (let i = 0; i < chain.length; i++) {
        let f = chain[i];
        output = output.then(f);
    }
    return output;
}

Затем, например, чтобы загрузить несколько URL-адресов один за другим, код будет таким:

// First build the array of promise generators:

let urls = [......];
let chain = [];
for (let i = 0; i < urls.length; i++) {
    chain.push(() => {
        return fetch(urls[i]);
    });
}

// Then execute the promises one after another:

promiseChain(chain).then(() => {
    console.info('All done');
});

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

person laurent    schedule 18.05.2017