¿Cómo utilizar cuándo enviar la carga de archivos de forma secuencial en una función que también es una promesa diferida?
-
26-12-2019 - |
Pregunta
Tengo la intención de cargar una serie de archivos usando jQuery.
Esta intención está envuelta en una función llamada uploadFilesUsingAjax()
;
var uploadFilesPromise = uploadFilesUsingAjax();
$.when(uploadFilesPromise).done(function (uploadFilesAjaxResult) {
// redirect to success page...
Necesito esperar a que todos los archivos se carguen correctamente antes de hacer otra cosa.
Adentro uploadFilesUsingAjax()
,
Escribí mi código de esta manera
function uploadFilesUsingAjax() {
var files = pages; // pages is a global variable which is an array of files
var url = "/users/" + currentUser.id + "/files.json";
var type = "POST";
console.info('files length:' + files.length);
if (files.length > 0) {
var promises=[];
for (var i = 0; i < files.length; i++) {
var data = new FormData();
var postData = {};
var file = files.getByIndex(i);
var key = i + 1;
if (typeof (file.id) !== "undefined" && file.id > 0) {
data.append(key, JSON.stringify(file));
} else {
data.append(key, file);
}
var request = $.ajax({
//this is the php file that processes the data
url: url,
//POST method is used
type: type,
//pass the data
data: data,
//Do not cache the page
cache: false,
xhr: function() {
// custom xhr
myXhr = $.ajaxSettings.xhr();
if(myXhr.upload) { // check if upload property exists
myXhr.upload.addEventListener('progress',updatePagesProgress, false); // for handling the progress of the upload
}
return myXhr;
},
// DO NOT set the contentType and processData
// see http://stackoverflow.com/a/5976031/80353
contentType: false,
processData: false,
//success
success: function (json) {
// json is already an object thanks to cakephp code to serialize
//if POST is a success expect no errors
if (json.error == null && json.result != null) {
currentUser = json.result.User;
// error
} else {
alert(json.error);
}
}
});
promises.push( request);
}
var promise = promises[0];
for (var i = 1; i < promises.length; i++) {
promise = promise.then(promises[i]);
}
return promise.done(function () { console.log('all!')});
Desafortunadamente, no pude cargar muchos archivos antes de ser redirigido a la página de éxito.
Probé varias soluciones de StackOverflow sobre cómo hacer esto.Hasta ahora nada funciona.Por favor avise.
Parte del código se ha truncado para ahorrar espacio.
Solución
Todas tus promesas son paralelas y no secuenciales.
Una promesa representa una ya corriendo tarea.Las promesas en JavaScript, a diferencia de las tareas de C# u otras abstracciones, ya están iniciadas.La forma de representar una tarea que no ha comenzado es una función que devuelve una promesa.
Desde promises[i]
ya es una promesa - cuando lo hagas promise.then(object)
no agrega un controlador .then sino que regresa inmediatamente. .then
ignora cualquier argumento que no sea una función.
Por eso regresa temprano, regresa tan pronto como se cumple la primera promesa.Tampoco necesitas el .when
.Cree una función que cree un proceso de carga como tal:
function createUploadTask(file,i){
return function(){
var data = new FormData();
var postData = {};
var file = files.getByIndex(i);
var key = i + 1;
if (typeof (file.id) !== "undefined" && file.id > 0) {
data.append(key, JSON.stringify(file));
} else {
data.append(key, file);
}
return $.ajax({...}); // return the promise
}
}
Ahora puedes asignar los archivos a tareas:
var tasks = files.map(createUploadTask);
Tenga en cuenta que ahora las tareas son cada una funciones que devuelven una promesa sobre la carga de un archivo.No son promesas.
Ahora puedes encadenarlos:
var p = tasks[0](); // start off the chain
for(var i = 1; i < tasks.length; i++){
// chain the next task, note, that we're passing a _function_ here
// when you return a promise from a `.then` it will fulfill when that promise
// fulfills, in our case the $.ajax
p = p.then(tasks[i]);
}
return p;
Ahora tampoco necesitas usar cuándo, ya que devuelves un soltero promesa.Supongo que no necesita el resultado real aquí (sino sólo para conocer el éxito/fracaso).
Simplemente haces:
function uploadFilesUsingAjax() {
// settings
if(tasks.length === 0){
// a method MUST always either return either synchronously or asynchronously never
// both, always return a promise. Otherwise you get API hell.
var d = $.Deferred();
d.reject(new Error("Called uploadFiles with no files to upload"));
return d.promise;
}
tasks = pages.map(createUploadTask)
var p = tasks[0](); // call first one
for(var i = 1; i < tasks.length; i++) p = p.then(tasks[i]);
return p;
}