Подтвердить что ты не робот

Async.js каждый получает индекс в итераторе

Я использую библиотеку caolan async.js, в частности метод .each.

Как вы получаете доступ к индексу в итераторе?

async.each(ary, function(element, callback){
  //do stuff here for each element in ary
  //how do I get access to the index?
}, function(err) {
  //final callback here
})
4b9b3361

Ответ 1

Вы можете использовать async.forEachOf - он вызывает обратный вызов iterator с индексом в качестве второго аргумента.

> async.forEachOf(['a', 'b', 'c'], function () {console.log(arguments)});
{ '0': 'a', '1': 0, '2': [Function] }
{ '0': 'b', '1': 1, '2': [Function] }
{ '0': 'c', '1': 2, '2': [Function] }

см. docs для получения дополнительной информации

Ответ 2

Update

С момента написания этого ответа теперь есть лучшее решение. Подробнее см. xuanji.

Оригинальные

Благодаря @genexp для простого и краткого примера в комментариях ниже...

async.each(someArray, function(item, done){
    console.log(someArray.indexOf(item));
});

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

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

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

Итак, я выкопал немного глубже (Ссылка на исходный код)

async.each = function (arr, iterator, callback) {
        callback = callback || function () {};
        if (!arr.length) {
            return callback();
        }
        var completed = 0;
        _each(arr, function (x) {
            iterator(x, only_once(function (err) {
                if (err) {
                    callback(err);
                    callback = function () {};
                }
                else {
                    completed += 1;
                    if (completed >= arr.length) {
                        callback(null);
                    }
                }
            }));
        });
    };

Как вы можете видеть, счетчик completed, который обновляется по мере завершения каждого обратного вызова, но не имеет фактической позиции индекса.

Кстати, нет никаких проблем с условиями гонки при обновлении счетчика completed, поскольку JavaScript чисто однопоточный под обложками.

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

async.iterator = function (tasks) {
    var makeCallback = function (index) {
        var fn = function () {
            if (tasks.length) {
                tasks[index].apply(null, arguments);
            }
            return fn.next();
        };
        fn.next = function () {
            return (index < tasks.length - 1) ? makeCallback(index + 1): null;
        };
        return fn;
    };
    return makeCallback(0);
};

Ответ 3

Просто yse async.forEachOf:

async.forEachOf(arr,  function(value, index, callback) { ... },...

См. документацию здесь.

Ответ 4

Метод async.each() будет перебирать массив параллельно, и он не предоставляет индекс элемента для повторного обратного вызова.

Итак, когда у вас есть:

function( done ){

  async.each(

    someArray,

    function( item, cb ){
      // ... processing

      cb( null );
    },

    function( err ){
      if( err ) return done( err );

      // ...
      done( null );
    }
  );
}

Пока вы можете использовать Array.indexOf(), чтобы найти его:

function( done ){

  async.each(

    someArray,

    function( item, cb ){
      // ... processing

      var index = someArray.indexOf( item );

      cb( null );
    },

    function( err ){
      if( err ) return done( err );

      // ...
      done( null );
    }
  );
}

Это требует поиска в памяти в массиве для КАЖДОЙ итерации массива. Для массивных массивов это может сильно замедлить работу.

Лучшим обходным решением может быть использование async.eachSeries() вместо этого и отслеживание индекса самостоятельно:

function( done ){

  var index = -1;
  async.eachSeries(

    someArray,

    function( item, cb ){

      // index is updated. Its first value will be `0` as expected
      index++;

      // ... processing

      cb( null );
    },

    function( err ){
      if( err ) return done( err );

      // ...
      done( null );
    }
  );
}

С eachSeries() вам гарантируется, что все будет сделано в правильном порядке.

Другим обходным решением, которое является асинхронным поддерживающим первым выбором, является повторение с Object.keys:

function( done ){

  async.each(

    Object.keys( someArray ),

    function( key, cb ){

      // Get the value from the key          
      var item = someArray[ key ];

      // ... processing

      cb( null );
    },

    function( err ){
      if( err ) return done( err );

      // ...
      done( null );
    }
  );
}

Надеюсь, это поможет.

Ответ 5

  • Решение indexOf медленное и не должно использоваться для больших массивов.
  • Решение everySeries от Merc не делает того, что вы хотите.
  • Эффективное обходное решение - это просто создать еще один массив с индексами:
someArrayWithIndexes = someArray.map(function(e, i) {return {obj: e, index: i}});
async.each(someArrayWithIndexes , function(item, done){
    console.log("Index:", item.index);
    console.log("Object:", item.obj);
});