Как разрешить обещание извне с помощью eventEmitter в nodeJS?

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

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

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

Я использую eventEmitter внутри моей функции, чтобы сигнализировать, когда эта работа над 1 элементом завершена, и теперь мы можем начать работу над следующим элементом.

Когда все элементы обработаны array.length==0, то обещание разрешено.

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

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

Я пытался использовать .once() вместо .on() при создании слушателя. Вроде проблему не решило...

function myFunc(objectToTest, testArray) {
    return new Promise(function (resolve, reject) {
        var array = [];

        for (let i = 0 ; i < testArray.length ; i++){
            if (objectToTest.name == testArray[i].name){
                array.push(testArray[i]);   
            }

        }


        eventEmitter.on('done-with-async-work', processArray);
        eventEmitter.emit('done-with-async-work')

        function processArray() {

            if (array.length > 0) {
                let itemInArray = array.shift();
                // some Async function
                auxFunc.asyncFunc(itemInArray).then(function (asyncResult)){
                    // Triggered when asyncFunc promise is resolved
                    eventEmitter.emit('done-with-async-work')
                }
            } else {
                console.log("Finished With All Async work!");
                resolve("Done with work!")
            }

        }

    });


}

person Curtwagner1984    schedule 16.10.2016    source источник
comment
Вы действительно только что создали рекурсивный вызов функции с помощью eventEmitters, но asyncFunc опирается на предыдущий результат, например водопад, или вы можете просто запустить все асинхронные операции одновременно и использовать Promise.all, чтобы увидеть, когда они все сделано.   -  person adeneo    schedule 16.10.2016
comment
@adeneo Асинхронная функция не зависит от предыдущего результата. Но он порождает дочерний процесс, и я не хочу, чтобы он порождал более 20 процессов одновременно. К сожалению, я не могу использовать Promise.all   -  person Curtwagner1984    schedule 16.10.2016
comment
Я действительно не вижу смысла использовать здесь генератор событий. Делает ли он что-нибудь еще, кроме того, что показано в вопросе? Тогда вы могли бы просто заменить вызов emit прямыми вызовами processArray - и вы действительно должны сделать это.   -  person Bergi    schedule 16.10.2016
comment
прослушиватель событий создается каждый раз при запуске функции - даже это не будет проблемой. Создание прослушивателя событий дешево. Обратите внимание, что каждый раз, когда функция выполняется, также выделяются промис, массив и как минимум 4 замыкания.   -  person Bergi    schedule 16.10.2016
comment
Если вы хотите дождаться каждой асинхронной операции, то, что у вас есть, возможно, с рекурсивным вызовом функции, мне кажется вполне приемлемым.   -  person adeneo    schedule 17.10.2016
comment
@Bergi Я действительно не вижу смысла использовать здесь эмиттер событий. Я использую эмиттер, чтобы узнать, когда асинхронная операция над 1 элементом завершена, чтобы я мог обработать другой элемент. Это правда, что первый вызов можно заменить прямым вызовом processArray. Кроме того, я беспокоюсь о создании дополнительных слушателей не из-за их стоимости, а потому, что они не уничтожаются, когда функция существует, так что это потенциальная утечка памяти.   -  person Curtwagner1984    schedule 17.10.2016
comment
@ Curtwagner1984 Нет, не только первый вызов, но и все вызовы emit. Особенно те, что в обратном вызове asyncFunc.   -  person Bergi    schedule 17.10.2016
comment
@Bergi Вы правы на 100%!   -  person Curtwagner1984    schedule 17.10.2016


Ответы (3)


Promise.all очень полезен, если вы хотите делать несколько вещей одновременно. Если вы хотите делать что-то линейным образом, вы можете связать обещания.

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

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

var somethings = [12,1,33,23,44,22,11,32,12,44,22,32];

function asyncSqr(v) {
  return new Promise(function (resolve, reject) {
    setTimeout(function () { resolve(v*v); }, 200);
  });
}


function myFunc(a) {
  var sum = 0;
  return new Promise(function (resolve, reject) {
    function doNext() {
      var next = a.shift();    
      if (!next) return resolve(sum);
      console.log('sending: ' + next);
      return asyncSqr(next).then(function (r) { sum += r; doNext(); });
    }  
    doNext();    
  });
}

myFunc(somethings).then(function (r) {
  console.log('result: ' + r);
});

person Keith    schedule 16.10.2016

Кажется, нет никакой веской причины использовать eventEmitter и вызывать его в следующей строке, просто вместо этого используйте Promise.all, а поскольку auxFunc.asyncFunc, похоже, возвращает значение, вы, вероятно, могли бы просто вернуть его на карте

function myFunc(objectToTest, testArray) {
    var promises = testArray.filter(function(item) {
        return objectToTest.name == item.name;
    }).map(function(itemInArray) {
        return auxFunc.asyncFunc(itemInArray);
    });

    Promise.all(promises).then(function(results) {
        console.log("Finished With All Async work!");
        // results would be all the results
    });
}

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

function myFunc(objectToTest, testArray) {
    return new Promise(function (resolve, reject) {
        var array = testArray.filter(function(item) {
            return objectToTest.name == item.name;
        });

        (function processArray(itemInArray) {
            auxFunc.asyncFunc(itemInArray).then(function (asyncResult) {
                if (array.length > 0) {
                    processArray(array.shift());
                } else {
                    resolve('all done');
                }
            });
        })(array.shift());
    });
}
person adeneo    schedule 16.10.2016
comment
Спасибо, это то, что я сделаю. Это похоже на ответ @Keith - person Curtwagner1984; 17.10.2016
comment
Это примерно тот же принцип, они оба используют рекурсивные вызовы функций, как и ваш код на самом деле, он просто делает это с событиями вместо прямых вызовов, и здесь используется IIFE, в то время как Кейт использует обычную функцию, чтобы делать то же самое и т. д. Картошка Картошка! - person adeneo; 17.10.2016

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

Для этой работы шаблон работает следующим образом:

function myFunc(objectToTest, testArray) {
    // first, pre-filter testArray.
    var array = testArray.filter(function() {
        return objectToTest.name == item.name;
    });

    // now use .reduce() to build a promise chain from the filtered array.
    return array.reduce(function(chain, item) {
        return chain.then(function(previousResult) {
            return auxFunc.asyncFunc(item).then(function(result) {
                // Triggered when asyncFunc promise is resolved
                // ... do something with the result ...
                return result; // make this step's result available to next step in the chain (if required).
            });
        });
    }, Promise.resolve(intialValue));
}

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

function myFunc(objectToTest, testArray) {
    return testArray.reduce(function(chain, item) {
        if(objectToTest.name !== item.name) {
            // Filter on-the-fly
            // Don't extend the chain at this iteration of .reduce()
            return chain;
        } else {
            // item.name meets the criterion, so add an async task to the chain.
            return chain.then(function(previousResult) {
                return auxFunc.asyncFunc(item).then(function(result) {
                    // Triggered when asyncFunc promise is resolved
                    // ...
                    return result; // make result available to next step in the chain.
                });
            });
        }
    }, Promise.resolve(intialValue));
}
person Roamer-1888    schedule 16.10.2016