web-dev-qa-db-fra.com

async.js chacun obtient l'index dans l'itérateur

J'utilise caolan's async.js library, plus précisément la méthode .each.

Comment avez-vous accès à l'index dans l'itérateur?

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
})
32
Kevin

Vous pouvez utiliser async.forEachOf - il appelle le rappel de son itérateur avec l'index comme second argument.

> 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] }

voir le docs pour plus d'infos

37
xuanji

Mettre à jour

Depuis que cette réponse a été écrite, il existe maintenant une meilleure solution. S'il vous plaît voir la réponse de xuanji pour plus de détails

Original

Merci à @genexp pour un exemple simple et concis dans les commentaires ci-dessous ...

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

Après avoir lu la documentation, je soupçonnais qu’il n’y avait aucun moyen d’accéder à un entier représentant la position dans la liste ...

Applique une fonction itérateur à chaque élément d'un tableau, en parallèle. L'itérateur est appelé avec un élément de la liste et un rappel pour la fin de l'opération. Si l'itérateur transmet une erreur à ce rappel, le rappel principal de chaque fonction est immédiatement appelé avec l'erreur.

Notez que, puisque cette fonction applique l'itérateur à chaque élément en parallèle, rien ne garantit que les fonctions de l'itérateur seront exécutées dans l'ordre.

J'ai donc creusé un peu plus profondément ( Lien au code source )

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);
                    }
                }
            }));
        });
    };

Comme vous pouvez le constater, un nombre completed est mis à jour à la fin de chaque rappel, mais aucune position d'index réelle.

Incidemment, la mise à jour du compteur completed ne pose pas de problème de concurrence car JavaScript est purement à thread unique sous les couvertures.

Edit: Après avoir creusé davantage dans l'itérateur, il vous semble que pourrait pouvoir référencer une variable index grâce aux fermetures ...

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);
};
34
Basic

Utilisez simplement async.forEachOf().

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

Pour plus de détails, voir documentation ici .

8
Alain Beauvois

La méthode async.each() itérera le tableau dans parallel et ne fournira pas l'index de l'élément au rappel itératif.

Alors quand tu as:

function( done ){

  async.each(

    someArray,

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

      cb( null );
    },

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

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

Pendant que vous POUVEZ utiliser Array.indexOf() pour le trouver:

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 );
    }
  );
}

Cela nécessite une recherche en mémoire dans le tableau pour CHAQUE itération du tableau. Pour les baies de grande taille, cela peut tout ralentir considérablement.

Une meilleure solution de contournement pourrait être d'utiliser async.eachSeries() à la place et de garder l'index vous-même:

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 );
    }
  );
}

Avec eachSeries(), vous avez la garantie que les choses se feront dans le bon ordre.

Une autre solution de contournement, qui est le premier choix du responsable d'async , consiste à itérer avec 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 );
    }
  );
}

J'espère que ça aide.

3
Merc
  1. la solution indexOf est lente et ne doit pas être utilisée pour les baies de grande taille.
  2. la solution eachSeries de Merc ne fait pas ce que vous voulez.
  3. Une solution efficace consiste simplement à créer un autre tableau avec des index:
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);
});
0
x3mka