javascript - then - cómo usar q.js promete trabajar con múltiples operaciones asíncronas
promises javascript w3schools (2)
Nota: esta pregunta también se publica de manera cruzada en la lista de correo de Q.js here .
Tuve una situación con varias operaciones asincrónicas y la respuesta que acepté indicó que utilizar Promises usando una biblioteca como q.js sería más beneficioso.
Estoy convencido de refactorizar mi código para usar Promises pero debido a que el código es bastante largo, recorté las partes irrelevantes y exporté las partes cruciales a un repositorio separado.
El repositorio está here y el archivo más importante es this .
El requisito es que quiero que pageSizes no esté vacío después de recorrer todos los archivos que se han eliminado.
El problema es que las operaciones FileAPI dentro de la función getSizeSettingsFromPage hacen que getSizeSettingsFromPage sea asíncrono.
Entonces no puedo colocar checkWhenReady (); Me gusta esto.
function traverseFiles() {
for (var i=0, l=pages.length; i<l; i++) {
getSizeSettingsFromPage(pages[i], calculateRatio);
}
checkWhenReady(); // this always returns 0.
}
Esto funciona, pero no es ideal. Prefiero llamar a CheckWhenReady solo UNA VEZ después de que todas las pages
hayan pasado por esta función calcularRatio con éxito.
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...
}
¿Cómo refactorizo el código para hacer uso de Promises en Q.js?
Mis sugerencias para hacer que esto funcione con Q.js están a continuación. La clave es que cada vez que desee hacer algo de manera asincrónica, debe devolver una promesa, y una vez que la tarea se complete, debe resolver esa promesa. Eso les permite a las personas que llaman de la función escuchar para que la tarea se complete y luego hacer otra cosa.
Como antes, he comentado mis cambios con // ***
. Avíseme si tiene más preguntas.
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;
}
Editar
defer
crea un Deferred , que contiene dos partes, una promise
y la función de resolve
. La promise
es devuelta por getSizeSettingsFromPage
. Básicamente, devolver una promesa es una forma de que una función diga "Te llamaré más tarde". Una vez que la función ha completado su tarea (en este caso una vez que se ha disparado el evento image.onload
) la función de resolve
se utiliza para resolver la promesa. Eso indica a todo lo que espera la promesa de que la tarea se ha completado.
Aquí hay un ejemplo más 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 función addAsync
agrega dos números pero espera 2 segundos antes de agregarlos. Como es asíncrono, devuelve una promesa ( deferred.promse
) y resuelve la promesa después de la espera de 2 segundos (resolución deferred.resolve
). Se puede then
método then
en una promesa y pasar una función de devolución de llamada para que se ejecute después de que se haya resuelto la promesa. La función de devolución de llamada se pasa en el valor de resolución de la promesa.
En su caso, teníamos una serie de promesas y teníamos que esperar a que todas se completaran antes de ejecutar una función, así que usamos Q.all . Aquí hay un ejemplo:
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
Parece que debe usar la función Q.all
para crear una promesa maestra correspondiente a cuando se cumplen todas las promesas getSizeSettings.
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 mayoría de las bibliotecas prometedoras deberían proporcionar un método similar para hacer este tipo de sincronización. Si alguna vez te encuentras con uno que no tiene, lo que podrías hacer es enganchar cada promesa individual a una devolución de llamada que incremente un contador compartido cuando se llame. Cuando su contador llega a n
usted sabe que ya ha resuelto todas las promesas para que pueda hacer que el incremento o devolución de llamada también llame a la devolución de llamada "real".
//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()
}
});
}
Tenga en cuenta que en estos casos hasta ahora las llamadas asincrónicas se pueden ejecutar en paralelo. Si necesita que se ejecuten de forma secuencial, generalmente la única forma es reescribir el bucle for como una función recursiva
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);