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