Tentando obter um documento do Excel através do ajax, encontrando problemas assíncronos

8

Estou adicionando JavaScript a um SharePoint e estou tentando escrever uma função que obtém um documento do Excel, ou melhor, os dados nele, de uma biblioteca. Eu já descobri como fazer isso, mas os arquivos em questão são muito grandes para serem retornados. Eu considerei definir manualmente o limite de tamanho para o ajax, mas não encontrei nenhuma maneira de fazer isso, portanto, o Plano B é enviar uma consulta para cada linha

$("#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 você pode ver, estou tentando contornar o problema assíncrono chamando a função recursivamente quando a solicitação retorna com dados, o que significa que ela deve continuar em execução. Quando coloco um console.log () imediatamente antes do retorno, percebo que ele está compilando os resultados conforme necessário. No entanto, o retorno da chamada readFileRow () volta antes do término do processo e consiste na resposta da primeira consulta ajax, aparentemente não tendo sido executada através da função .done (). Tentei remover o retorno na frente da chamada ajax, mas isso só causa uma exceção, pois o retorno não é mais selecionável e, portanto, não possui uma função .done ().

Alguém pode identificar o que há de errado com essa função? As funções assíncronas não são o meu forte e estou bastante perplexo.

B. Allred
fonte

Respostas:

2

Parece que a resposta foi simplesmente que eu não entendi completamente o que vários métodos relacionados às funções assíncronas estavam fazendo. Reestruturei a função da seguinte maneira e a fiz funcionar como esperado:

    $("#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
fonte
0

Não posso testar sua implementação exata, mas eis como eu fiz isso anteriormente:

Para a chamada do ajax readFileRow, use em .thenvez de, .donepois isso retornará uma promessa para o valor de retorno da .thenfunção em vez do próprio objeto ajax.

Então você pode simplesmente retornar finalResultssem o quando e as chamadas recursivas e iniciais readFileRownecessárias para awaitesse retorno.

Também não sou muito versado em promessas e codificação assíncrona, mas foi assim que fiz coisas semelhantes 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
fonte
0

Em vez de tornar as chamadas simultâneas (aguarde até solicitar retorno e só depois ligue para a próxima), você pode chamá-las em paralelo usando Promise.all.

Embora o código abaixo não esteja usando seu código (porque não consigo fazê-lo realmente funcionar), ele demonstra como deve agir. Se não refletir 100% do seu caso, me avise.

A lógica é:

  1. Buscando o número total de linhas para que possamos saber a quantidade de solicitações que devemos criar - precisamos aguardar por essa solicitação, pois precisamos de seus dados para o restante das solicitações.
  2. Fazendo um array com todas as promessas e passá-lo para Promise.all. O thenretornará todos os dados combinados.

Deixe-me saber se o exemplo não 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
fonte
Isso realmente não me ajudou muito. Eu não acho que você esteja necessariamente errado, mas não consegui tirar a lição que você estava ensinando para aplicar ao meu código.
precisa
Justo. Embora seja um importante desempenho sábio ..
Mosh Feu