Intentando obtener un documento de Excel a través de ajax, encontrando problemas asincrónicos

8

Estoy agregando JavaScript a un SharePoint y estoy tratando de escribir una función que obtenga un documento de Excel, o más bien los datos que contiene, de una biblioteca. Ya he descubierto cómo hacerlo, pero los archivos en cuestión son demasiado grandes para ser devueltos. He considerado establecer manualmente el límite de tamaño para ajax, pero no he encontrado ninguna forma de hacerlo, por lo que el Plan B es enviar una consulta para cada fila

$("#button").click(function() {
			readFileRow(libraryUrl, "FileName.xlsx", "Sheet1", 1, "E", [])
			.done(function(cells) {
				console.log(xmlToJson(cells.documentElement));
			});
		});

/**
	 * Reads a file in SharePoint Library via ExcelRest and returns the document
	 *
	 * @params string libraryUrl
	 * @params string fileName
	 * @params string sheetName
	 * @params int rowNum
	 * @params string lastColumn
	 * @params string[][] finalResults
	 *
	 * @returns string[][]
	**/
	function readFileRow(libraryUrl, fileName, sheetName, rowNum, lastColumn, finalResults) {
		var fileUrl = libraryUrl + "/" + fileName + "/Model/Ranges('" + sheetName + "!A" + rowNum + "|" + lastColumn + rowNum + "')?$format=atom";

		return $.ajax({
			url: fileUrl,
			type: "GET",
			error: function (request, status, error) {
				console.log(error);
			},
		}).done(function(data) {
			jsonData = xmlToJson(data.documentElement);
			cells = serializeRange(jsonData);

			if(cells) {
				finalResults.push(cells);
				rowNum++;
				finalResults = readFileRow(libraryUrl, fileName, sheetName, rowNum, lastColumn, finalResults);
			}

			return $.when(finalResults);
		});
	}

Como puede ver, estoy tratando de solucionar el problema asincrónico llamando a la función de forma recursiva cuando la solicitud vuelve con datos, lo que significa que tiene que seguir ejecutándose. Cuando coloco un console.log () justo antes del regreso, puedo ver que está compilando los resultados como lo necesito. Sin embargo, el retorno de llamar a readFileRow () regresa antes de que finalice el proceso y consiste en la respuesta de la primera consulta ajax, que aparentemente no se ejecutó a través de la función .done (). Intenté eliminar el retorno frente a la llamada ajax, pero esto solo causa una excepción, ya que el retorno ya no es posible y, por lo tanto, no tiene una función .done ().

¿Alguien puede detectar lo que está mal con esta función? Las funciones asíncronas no son mi fuerte y estoy bastante perplejo.

B. Allred
fuente

Respuestas:

2

Parece que la respuesta fue simplemente que no entendía completamente qué estaban haciendo varios métodos relacionados con las funciones asíncronas. Reestructuré la función de la siguiente manera y conseguí que funcionara como se esperaba:

    $("#updateClusterDetails").click(function() {
        console.log("Update initiated");
        var baseFile = "AIF%20Test.xlsx";

        $.when(readFileRow(libraryUrl, baseFile, "Sheet1", 1, "E", new Array())).then(function(results) {
            console.log(results);
        });
    });

/**
 * Reads a file in SharePoint Library via ExcelRest and returns the document
 *
 * @params string libraryUrl
 * @params string fileLocation
 *
 * @returns Document/HTML
**/
function readFile(libraryUrl, fileLocation) {
    // The base url for the SharePoint document library
    var fileUrl = libraryUrl + fileLocation;

    return $.ajax({
        url: fileUrl,
        type: "GET",
        error: function (request, status, error) {
            console.log(error);
        },
        success: function(data) {
            return $.when(data);
        }
    });
}

    /**
 * Reads a series of rows in an Excel file in SharePoint Library via ExcelRest and returns the row data
 *
 * @params string libraryUrl
 * @params string fileName
 * @params string sheetName
 * @params int rowNum
 * @params string lastColumn
 * @params string[][] currentResults
 *
 * @returns string[][]
**/
function readFileRows(libraryUrl, fileName, sheetName, rowNum, lastColumn, currentResults) {
    var fileUrl = "/" + fileName + "/Model/Ranges('" + sheetName + "!A" + rowNum + "|" + lastColumn + rowNum + "')?$format=atom";

    return readFile(libraryUrl, fileUrl)
    .then(function(data) {
        jsonData = xmlToJson(data.documentElement);
        cells = serializeRange(jsonData);

        if(!cells) {
            return $.when(currentResults);
        }
        else {
            currentResults.push(cells);
            return $.when(readFileRow(libraryUrl, fileName, sheetName, rowNum + 1, lastColumn, currentResults));
        }

    });
}
B. Allred
fuente
0

No puedo probar su implementación exacta, pero así es como lo hice anteriormente:

Para la llamada ajax readFileRow, use en .thenlugar de .doneya que esto devolverá una promesa para el valor de retorno de la .thenfunción en lugar del objeto ajax en sí.

Entonces puede regresar finalResultssin el cuándo, y las llamadas recursivas e iniciales readFileRownecesarias para awaiteste retorno.

Tampoco estoy muy versado en promesas y codificación asíncrona, pero así es como he hecho cosas similares anteriormente.

$("#button").click(function() {
	let cells = await readFileRow(libraryUrl, "FileName.xlsx", "Sheet1", 1, "E", [])
	console.log(xmlToJson(cells.documentElement));
});

/**
	 * Reads a file in SharePoint Library via ExcelRest and returns the document
	 *
	 * @params string libraryUrl
	 * @params string fileName
	 * @params string sheetName
	 * @params int rowNum
	 * @params string lastColumn
	 * @params string[][] finalResults
	 *
	 * @returns string[][]
	**/
	function readFileRow(libraryUrl, fileName, sheetName, rowNum, lastColumn, finalResults) {
		var fileUrl = libraryUrl + "/" + fileName + "/Model/Ranges('" + sheetName + "!A" + rowNum + "|" + lastColumn + rowNum + "')?$format=atom";

		return $.ajax({
			url: fileUrl,
			type: "GET",
			error: function (request, status, error) {
				console.log(error);
			},
		}).then(function(data) {
			jsonData = xmlToJson(data.documentElement);
			cells = serializeRange(jsonData);

			if(cells) {
				finalResults.push(cells);
				rowNum++;
				finalResults = await readFileRow(libraryUrl, fileName, sheetName, rowNum, lastColumn, finalResults);
			}

			return finalResults;
		});
	}

Tugzrida
fuente
0

En lugar de hacer que las llamadas sean simultáneas (espere hasta solicitar la devolución y solo luego llame a la siguiente), puede llamarlas en paralelo usando Promise.all.

Si bien el siguiente código no usa su código en absoluto (porque no puedo hacer que realmente funcione), demuestra cómo debería actuar. Si no refleja su caso al 100%, avíseme.

La lógica es:

  1. Obteniendo el número total de filas para poder saber cuánta solicitud deberíamos crear, tenemos que esperar esta solicitud porque necesitamos sus datos para el resto de las solicitudes.
  2. Hacer una matriz con todas las promesas y pasarla a Promise.all. El thendevolverá todos los datos combinados.

Avíseme si el ejemplo no está claro.

const data = new Array(10).fill().map((_, i) => ({
  foo: `bar${i}`
}));

/* simulate the ajax call to get the total number of rows
 TODO replace it with the actual call */
function getRowsCount() {
  return Promise.resolve(data.length);
}

/* simulate the ajax call to get row's data by index
 TODO replace it with the actual call*/
function getRow(index) {
  return Promise.resolve(data[index]);
}

function getData() {
  return getRowsCount()
    .then(rowCount => {
      const allRequests = new Array(rowCount).fill().map((_, index) => getRow(index));
      return Promise.all(allRequests)
    });
}

getData().then(data => {
  console.log(data);
});

Mosh Feu
fuente
Esto realmente no me ayudó mucho. No creo que estés necesariamente equivocado, pero no pude obtener la lección que estabas enseñando para aplicar a mi código.
B. Allred
Lo suficientemente justo. Aunque es importante en cuanto a rendimiento ..
Mosh Feu