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
})
Ответы
Ответ 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);
});