web-dev-qa-db-fra.com

comment utiliser q.js promet de travailler avec plusieurs opérations asynchrones

Remarque: Cette question est également transposée dans la liste de diffusion Q.js sur ici .


j'ai eu une situation avec plusieurs opérations asynchrones et le réponse que j'ai acceptée a souligné que l'utilisation de Promises en utilisant une bibliothèque telle que q.js serait plus bénéfique.

Je suis convaincu de refactoriser mon code pour utiliser Promises mais parce que le code est assez long, j'ai coupé les parties non pertinentes et exporté les parties cruciales dans un référentiel séparé.

Le dépôt est ici et le fichier le plus important est this .

L'exigence est que je souhaite que pageSizes ne soit pas vide après avoir parcouru tous les fichiers glissés-déposés.

Le problème est que les opérations FileAPI à l'intérieur de getSizeSettingsFromPage entraînent getSizeSettingsFromPage à être async.

Je ne peux donc pas placer checkWhenReady (); comme ça.

function traverseFiles() {
  for (var i=0, l=pages.length; i<l; i++) {
    getSizeSettingsFromPage(pages[i], calculateRatio);   
  }
  checkWhenReady(); // this always returns 0.
}

Cela fonctionne, mais ce n'est pas idéal. Je préfère appeler checkWhenReady UNE FOIS après que tous les pages ont subi avec succès cette fonction CalculateRatio.

function calculateRatio(width, height, filename) {
  // .... code 
  pageSizes.add(filename, object);
  checkWhenReady(); // this works but it is not ideal. I prefer to call this method AFTER all the `pages` have undergone calculateRatio
  // ..... more code...
}

Comment puis-je refactoriser le code pour utiliser Promises dans Q.js?

23
Kim Stacks

Mes suggestions pour que cela fonctionne avec Q.js sont ci-dessous. La clé est que chaque fois que vous voulez faire quelque chose de manière asynchrone, vous devez renvoyer une promesse, et une fois la tâche terminée, vous devez résoudre cette promesse. Cela permet aux appelants de la fonction d'écouter la fin de la tâche, puis de faire autre chose.

Comme précédemment, j'ai commenté mes modifications avec // ***. Dis moi si tu as d'autres questions.

        function traverseFiles() {
            // *** Create an array to hold our promises
            var promises = [ ];
            for (var i=0, l=pages.length; i<l; i++) {
                // *** Store the promise returned by getSizeSettingsFromPage in a variable
                promise = getSizeSettingsFromPage(pages[i]);
                promise.then(function(values) {
                    var width = values[0],
                        height = values[1],
                        filename = values[2];
                    // *** When the promise is resolved, call calculateRatio
                    calculateRatio(width, height, filename);
                });
                // *** Add the promise returned by getSizeSettingsFromPage to the array
                promises.Push(promise);
            }
            // *** Call checkWhenReady after all promises have been resolved
            Q.all(promises).then(checkWhenReady);
        }

        function getSizeSettingsFromPage(file) {
            // *** Create a Deferred
            var deferred = Q.defer();
            reader = new FileReader();
            reader.onload = function(evt) {
                var image = new Image();
                image.onload = function(evt) {
                    var width = this.width;
                    var height = this.height;
                    var filename = file.name;
                    // *** Resolve the Deferred
                    deferred.resolve([ width, height, filename ]);
                };
                image.src = evt.target.result;
            };
            reader.readAsDataURL(file);
            // *** Return a Promise
            return deferred.promise;
        }

Éditer

defer crée un différé, qui contient deux parties, un promise et la fonction resolve. promise est retourné par getSizeSettingsFromPage. Fondamentalement, renvoyer une promesse est un moyen pour une fonction de dire "je vous répondrai plus tard". Une fois la fonction terminée, sa tâche (dans ce cas, une fois que le image.onload événement s'est déclenché) la fonction resolve est utilisée pour résoudre la promesse. Cela indique à tout ce qui attend la promesse que la tâche est terminée.

Voici un exemple plus simple:

function addAsync(a, b) {
    var deferred = Q.defer();
    // Wait 2 seconds and then add a + b
    setTimeout(function() {
        deferred.resolve(a + b);
    }, 2000);
    return deferred.promise;
}

addAsync(3, 4).then(function(result) {
    console.log(result);
});
// logs 7 after 2 seconds

La fonction addAsync ajoute deux nombres mais attend 2 secondes avant de les ajouter. Comme il est asynchrone, il renvoie une promesse (deferred.promse) et résout la promesse après 2 secondes d'attente (deferred.resolve). La méthode then peut être appelée sur une promesse et passée une fonction de rappel à exécuter après la résolution de la promesse. La fonction de rappel est passée dans la valeur de résolution de la promesse.

Dans votre cas, nous avions un tableau de promesses et nous devions attendre que toutes soient effectuées avant d'exécuter une fonction, nous avons donc utilisé - Q.all. Voici un exemple:

function addAsync(a, b) {
    var deferred = Q.defer();
    // Wait 2 seconds and then add a + b
    setTimeout(function() {
        deferred.resolve(a + b);
    }, 2000);
    return deferred.promise;
}

Q.all([
    addAsync(1, 1),
    addAsync(2, 2),
    addAsync(3, 3)
]).spread(function(result1, result2, result3) {
    console.log(result1, result2, result3);
});
// logs "2 4 6" after approximately 2 seconds
45
Nathan Wall

Il semble que vous devriez utiliser le Q.all fonction pour créer une promesse principale correspondant au moment où toutes les promesses getSizeSettings sont remplies.

https://github.com/kriskowal/q#combination

var ps = [];
for (var i=0, l=pages.length; i<l; i++) {
   ps[i] = getSizeSettingsFromPage(pages[i], calculateRatio);   
}

Q.all(ps).then(function(){ callWhenReady() })

La plupart des bibliothèques de promesses devraient fournir une méthode similaire pour effectuer ce type de synchronisation. Si jamais vous en rencontrez une qui ne fait pas ce que vous pourriez faire, c'est accrocher chaque promesse individuelle à un rappel qui incrémente un compteur partagé lors de son appel. Lorsque votre compteur atteint n, vous savez que vous avez déjà résolu toutes les promesses afin que le rappel incrémenteur puisse également appeler le rappel "réel".

//If you did not have Q.all available
//Or had to code this without a promise library

var to_go = pages.length;
for (var i=0, l=pages.length; i<l; i++) {
   getSizeSettingsFromPage(pages[i], calculateRatio)
   .then(function(){
       to_go--;
       if(to_go == 0){
           callWhenReady()
       }
   });
}

Notez que dans ces cas, jusqu'à présent, les appels asynchrones sont autorisés à s'exécuter en parallèle. Si vous en avez besoin pour s'exécuter séquentiellement, la seule façon est généralement de réécrire la boucle for en tant que fonction récursive

var go = function(i){
    if(i>=pages.length){
        return call_next_step()
    }else{
        return do_ith_calculation(i)
        .then(function(){
            return go(i+1)
        })
    }
};
go(0);
2
hugomg