adjust onload function to be used with async/await [duplicate] - javascript

This question already has answers here:
How do I promisify native XHR?
(6 answers)
Closed 3 months ago.
I'm working on converting an xlsx I get from a URL to a JSON object in the Browser.
This answer works --> https://stackoverflow.com/a/52237535/5079799
But, I can't get the code to wait for the reponse. All the answers online seem to be about images and/or using an input filereader, but I'm fetching a URL.
How I can wrap this all in a function that says:
Get XLSX from URL
Convert to JSON
Return JSON
Here is what I've been messing with so far, but it always ends with the outside variables unset but the inside works correctly.
async function Outside_Test(){
var reso = await Get_JSON()
console.log('reso_out')
console.log(reso)
}
async function Get_JSON() {
var url = "http://myspreadsheet.xlsx"
var oReq = new XMLHttpRequest();
oReq.open("GET", url, true);
oReq.responseType = "arraybuffer";
//oReq.onload =
return oReq.send()
.then(function (oReq) {
var arraybuffer = oReq.response;
/* convert data to binary string */
var data = new Uint8Array(arraybuffer);
var arr = new Array();
for (var i = 0; i != data.length; ++i) arr[i] = String.fromCharCode(data[i]);
var bstr = arr.join("");
/* Call XLSX */
var workbook = XLSX.read(bstr, {
type: "binary"
});
/* DO SOMETHING WITH workbook HERE */
var first_sheet_name = workbook.SheetNames[0];
/* Get worksheet */
var worksheet = workbook.Sheets[first_sheet_name];
var reso = (XLSX.utils.sheet_to_json(worksheet, {
raw: true
}));
console.log('inside-reso')
return reso
})
}

You'll want to return a Promise from Get_JSON that resolves when .onload is called
something like
function Get_JSON() {
return new Promise((resolve, reject) => {
var url = "http://myspreadsheet.xlsx"
var oReq = new XMLHttpRequest();
oReq.open("GET", url, true);
oReq.responseType = "arraybuffer";
oReq.onload = function () {
/* convert data to binary string */
/* Call XLSX */
/* DO SOMETHING WITH workbook HERE */
/* Get worksheet */
console.log('inside-reso')
resolve(reso);
});
oReq.onerror = reject;
oReq.send() ;
});
}
Note: no need for Get_JSON to be async ... since you never need to await
Another alternative I guess is
async function Get_JSON() {
const arrayBuffer = await new Promise((resolve, reject) => {
var url = "http://myspreadsheet.xlsx"
var oReq = new XMLHttpRequest();
oReq.open("GET", url, true);
oReq.responseType = "arraybuffer";
oReq.onload = () => resolve(oReq.response);
oReq.onerror = reject;
oReq.send();
});
/* convert data to binary string */
var data = new Uint8Array(arraybuffer);
// ...
/* Call XLSX */
// ...
/* DO SOMETHING WITH workbook HERE */
// ...
/* Get worksheet */
// ...
return reso;
}
I kinda like this way, using async/await makes it clear this will return a Promise

Related

I want to store data samples from an excel sheet into an array in javascript

I am using sheetJS in order to manipulate excel sheets. My goal is to extract the value of a cell and store it in an array as raw data for later statistical analysis and graphing.
Here is what the function looks like:
function getSheetData()
{
let rawData = [];
/* set up XMLHttpRequest */
var url = "test.xlsx";
var oReq = new XMLHttpRequest();
oReq.open("GET", url, true);
oReq.responseType = "arraybuffer";
oReq.send();
oReq.onload = function (e) {
var arraybuffer = oReq.response;
/* convert data to binary string */
var data = new Uint8Array(arraybuffer);
var arr = new Array();
for (var i = 0; i != data.length; ++i) arr[i] = String.fromCharCode(data[i]);
var bstr = arr.join("");
/* Call XLSX */
var workbook = XLSX.read(bstr, {
type: "binary"
});
/* DO SOMETHING WITH workbook HERE */
var sheet_name_list = workbook.SheetNames;
// var worksheet;
sheet_name_list.forEach(function(y) { /* iterate through sheets */
var worksheet = workbook.Sheets[y];
for (z in worksheet) {
/* all keys that do not begin with "!" correspond to cell addresses */
if(z[0] === '!') continue;
// console.log(z + " = " + JSON.stringify(worksheet[z].v));
rawData.push(worksheet[z].v);
}
});
/* Get worksheet */
// console.log(XLSX.utils.sheet_to_json(worksheet, {
// raw: true
// }));
console.log("raw data = " + rawData);
}
// console.log(rawData);
return rawData;
}
The console.log defined as 'raw data' shows all the numbers in one array just how I need it. However, the array named "rawData" returns as undefined by the end of the function.
I am calling the function here:
window.onload = function()
{
const data = getSheetData();
const BenfordTable = calculateBenford(data);
printAsTable(BenfordTable);
printAsGraph(BenfordTable);
}
I get data as an empty array
I have included a picture of the browser window
screenshot of console results in google chrome
data is an empty array because getSheetData() is an asynchronous function - that is to say, you are making an XMLHttpRequest call from within it. If you put console logs within your onload handler and right before your return statement, you will see that the latter runs first. The issue is that when your function returns, the call to the server will not have yet returned.
There are a few different ways of writing asynchronous code, but I think you should start off by passing a callback function to getSheetData() which will be called from within your onload handler. This callback function will be what handles rawData.
Here's roughly how you might do this. I have omitted some of the existing code for brevity, but obviously you will need it.
function getSheetData(callback)
{
let rawData = [];
// ...other code
oReq.onload = function (e) {
var arraybuffer = oReq.response;
// ...other code
callback(rawData); // <-- add this
}
// no need to return anything!
// return rawData;
}
window.onload = function()
{
getSheetData(function (data) {
const BenfordTable = calculateBenford(data);
printAsTable(BenfordTable);
printAsGraph(BenfordTable);
});
}
There are other things you could use to write such code, such as Promises, but that's probably something else to look into. We're also not doing any error handling here, which is also an important concept. The main takeaway here is that you are only handling the rawData once the call to the server has completed.

Read the excel file from the specific location

I don't want to use this way(link)
because I don't want to open new window for file selection.
I just want to load & read excel file when I click some button.
But this is not work.. help me to load the excel file.
MYCODE:
const FILE_NAME = "C:/Users/A/project_list.xlsx";
function LoadSpread(json) {
jsonData = json;
workbook.fromJSON(json);
workbook.setActiveSheet("Revenues (Sales)");
}
function excelExport(fileName) {
console.log(fileName);
var excelUrl = fileName;
var oReq = new XMLHttpRequest();
oReq.open("get", excelUrl, true);
oReq.responseType = "blob";
oReq.onload = function() {
var blob = oReq.response;
excelIO.open(blob, LoadSpread, function(message) {
console.log(message);
});
};
oReq.send(null);
}
function chkPrtNum(event) {
var fileName = FILE_NAME;
excelExport(fileName);
}
Try creating a small server for the same, you will easily get your job done. – Raghu Chahar Jan 28 at 9:50
This was very helpful

Increment variable in constructor function (JS)

I am fetching the data from 2 different APIs and I wrote a constructor function that will start XMLHttpRequest based on the new instance of the object creation (if it makes any sense...). Once the data is fetched and parsed I want to save it into different named variables. For instance: trial1data, trial2data. At the minute the new instances of the objects overwrite the data variable. Code below:
var api = "http://www.filltext.com/?rows=10&fname={firstName}&lname={lastName}&pretty=true";
var api2 = "http://www.filltext.com/?rows=10&fname={firstName}&lname={lastName}&tel={phone|format}&address={streetAddress}&city={city}&state={usState|abbr}&zip={zip}&pretty=true";
function FetchData(apiType) {
var r = new XMLHttpRequest();
this.apiType = apiType;
this.request = function() {
r.open("GET", apiType, true);
r.onload = function() {
var data = JSON.parse(r.responseText);
}
r.send(null);
}
}
trial1 = new FetchData (api);
trial1.request();
trial2 = new FetchData (api2);
trial2.request();
Thanks for the XMLHttpRequest tip, but the issue was to save each data into separate variables such as trial1data and trial2data (or anything else that has got a meaning and I can re-use later on), based on how many new objects I will create.
Your var r = new XMLHttpRequest(); is common.
You need to move it inside the function to create separate request everytime the constructor function is called.
function FetchData(apiType) {
this.apiType = apiType;
this.request = function() {
var r = new XMLHttpRequest();
r.open("GET", apiType, true);
r.onload = function() {
var data = JSON.parse(r.responseText);
console.log(data);
}
r.send(null);
}
}
You should put the request object creation inside the constructor:
function FetchData(apiType) {
var r = new XMLHttpRequest();
this.apiType = apiType;
this.done = new Promise( res => {
r.onload = function() {
res(JSON.parse(r.responseText) );
};
});
this.request = function() {
r.open("GET", apiType, true);
r.send(null);
};
}
So you can do:
const req = new FetchData("whatever");
req.request();
req.done.then(console.log);
Create new instance on var r = new XMLHttpRequest(); inside of constructor, or as a better approach, make it an argument for a constructor and inject new XMLHttpRequets object for each.
To answer the second part of your question, you could store response data in object's property and access it directly or getter interface. So instead of
r.onload = function() {
var data = JSON.parse(r.responseText);
}
Do something like:
function FetchData(apiType) {
var self = this;
this.apiType = apiType;
this.request = function() {
var r = new XMLHttpRequest();
return new Promise(function(resolve, reject) {
r.open("GET", apiType, true);
r.onload = function() {
self.data = JSON.parse(r.responseText);
resolve(self.data);
}
r.send(null);
}
}
Then
trial1 = new FetchData (api);
var trial1resp;
trial1.request().then(function(data) {
trial1resp = data;
}
The last assignment is just to show how is response stored. You must handle async processess to achieve your goal.
You could read little bit more about promisses and how to handle xhr async tasks here https://developers.google.com/web/fundamentals/primers/promises

Protobuf : WebApi -> JS - Decoded object is empty

I would like to send an object from a WebApi controller to an Html page through an Ajax Request.
When I receive the object in JS, it's empty. But server-side the object isn't empty because when I look at the byte[].length it's greater than 0.
Server-side, I use the dll provided by Google.
JS side, I use the ProtobufJS library. This is my .proto file :
syntax="proto3";
message Container {
repeated TestModel2 Models = 1;
}
message TestModel2 {
string Property1 = 1;
bool Property2 = 2;
double Property3 = 3;
}
Server code :
var container = new Container();
var model = new TestModel2
{
Property1 = "Test",
Property2 = true,
Property3 = 3.14
};
container.Models.Add(model);
Base64 data :
ChEKBFRlc3QQARkfhetRuB4JQA==
JS decoding :
var ProtoBuf = dcodeIO.ProtoBuf;
var xhr = ProtoBuf.Util.XHR();
xhr.open(
/* method */ "GET",
/* file */ "/XXXX/Protobuf/GetProtoData",
/* async */ true
);
xhr.responseType = "arraybuffer";
xhr.onload = function (evt) {
var testModelBuilder = ProtoBuf.loadProtoFile(
"URL_TO_PROTO_FILE",
"Container.proto").build("Container");
var msg = testModelBuilder.decode64(xhr.response);
console.log(JSON.stringify(msg, null, 4)); // Correctly decoded
}
xhr.send(null);
Result object in JS console :
{
"Models": []
}
bytebuffer.js
protobuf.js v5.0.1
Finally i solved the problem by myself.
It was the client-side which was in fault.
In fact the xhr.response is JSON format so it was between double quotes "ChEKBFRlc3QQARkfhetRuB4JQA==". I had to JSON.parse my response.enter code here
I removed the xhr.responseType = "arraybuffer";
Here is my code now :
var ProtoBuf = dcodeIO.ProtoBuf;
var xhr = ProtoBuf.Util.XHR();
xhr.open(
/* method */ "GET",
/* file */ "/XXXX/Protobuf/GetProtoData",
/* async */ true
);
// xhr.responseType = "arraybuffer"; <--- Removed
xhr.onload = function (evt) {
var testModelBuilder = ProtoBuf.loadProtoFile(
"URL_TO_PROTO_FILE",
"Container.proto").build("Container");
var msg = testModelBuilder.decode64(JSON.parse(xhr.response)); <-- Parse the response in JSON format
console.log(msg); // Correctly decoded
}
xhr.send(null);

Convert URL to File or Blob for FileReader.readAsDataURL

Reference: FileReader.readAsDataURL
Considering the following example:
function previewFile(file) {
var reader = new FileReader();
reader.onloadend = function () {
console.log(reader.result);
}
reader.readAsDataURL(file);
}
It states:
instanceOfFileReader.readAsDataURL(blob);
blob: The Blob or File from which to read.
How can a local file URL like: 'file:///C:/path-to/root.png' be
passed to the readAsDataURL()
Is FileReader() available in a Firefox Addon?
To convert a URL to a Blob for FileReader.readAsDataURL() do this:
var request = new XMLHttpRequest();
request.open('GET', MY_URL, true);
request.responseType = 'blob';
request.onload = function() {
var reader = new FileReader();
reader.readAsDataURL(request.response);
reader.onload = function(e){
console.log('DataURL:', e.target.result);
};
};
request.send();
Expanding on Felix Turner s response, here is how I would use this approach with the fetch API.
async function createFile(){
let response = await fetch('http://127.0.0.1:8080/test.jpg');
let data = await response.blob();
let metadata = {
type: 'image/jpeg'
};
let file = new File([data], "test.jpg", metadata);
// ... do something with the file or return it
}
createFile();
The suggested edit queue is full for #tibor-udvari's excellent fetch answer, so I'll post my suggested edits as a new answer.
This function gets the content type from the header if returned, otherwise falls back on a settable default type.
async function getFileFromUrl(url, name, defaultType = 'image/jpeg'){
const response = await fetch(url);
const data = await response.blob();
return new File([data], name, {
type: data.type || defaultType,
});
}
// `await` can only be used in an async body, but showing it here for simplicity.
const file = await getFileFromUrl('https://example.com/image.jpg', 'example.jpg');
Try this I learned this from #nmaier when I was mucking around with converting to ico:
Well i dont really understand what array buffer is but it does what we need:
function previewFile(file) {
var reader = new FileReader();
reader.onloadend = function () {
console.log(reader.result); //this is an ArrayBuffer
}
reader.readAsArrayBuffer(file);
}
notice how i just changed your readAsDataURL to readAsArrayBuffer.
Here is the example #nmaier gave me:
https://stackoverflow.com/a/24253997/1828637
it has a fiddle
if you want to take this and make a file out of it i would think you would use file-output-stream in the onloadend
This information is outdated as of now, but cannot be deleted.
You can create File instances just by specifying a path when your code is chrome-privileged:
new File("/path/to/file");
File is a sub-class of Blob, so all File instances are also valid Blobs.
Please note that this requires a platform path, and not a file URL.
Yes, FileReader is available to addons.
File and FileReader are available in all windows. If you want to use them in a non-window scope (like bootstrap.js or a code module), you may use nsIDOMFile/nsIDOMFileReader.
Here is my code using async awaits and promises
const getBlobFromUrl = (myImageUrl) => {
return new Promise((resolve, reject) => {
let request = new XMLHttpRequest();
request.open('GET', myImageUrl, true);
request.responseType = 'blob';
request.onload = () => {
resolve(request.response);
};
request.onerror = reject;
request.send();
})
}
const getDataFromBlob = (myBlob) => {
return new Promise((resolve, reject) => {
let reader = new FileReader();
reader.onload = () => {
resolve(reader.result);
};
reader.onerror = reject;
reader.readAsDataURL(myBlob);
})
}
const convertUrlToImageData = async (myImageUrl) => {
try {
let myBlob = await getBlobFromUrl(myImageUrl);
console.log(myBlob)
let myImageData = await getDataFromBlob(myBlob);
console.log(myImageData)
return myImageData;
} catch (err) {
console.log(err);
return null;
}
}
export default convertUrlToImageData;
I know this is an expansion off of #tibor-udvari's answer, but for a nicer copy and paste.
async function createFile(url, type){
if (typeof window === 'undefined') return // make sure we are in the browser
const response = await fetch(url)
const data = await response.blob()
const metadata = {
type: type || 'video/quicktime'
}
return new File([data], url, metadata)
}
I ended up wanting something similar to this but without having to pass the type for the file or the filename, so I made my own example based on #Felix Turner's example. I used the content-disposition header for the filename first in case the file is coming back from an API endpoint, but use the last part of the URL path if that header doesn't exist.
function getFilenameFromContentDisposition(res) {
let filename = null;
const disposition = res.headers.get("content-disposition");
if (disposition?.includes("attachment")) {
const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
const matches = filenameRegex.exec(disposition);
if (matches?.[1]) {
filename = matches[1].replace(/['"]/g, "");
// Sometimes the filename comes in a URI encoded format so decode it
filename = decodeURIComponent(filename);
// Sometimes the filename starts with UTF-8, remove that
filename = filename.replace(/^UTF-8/i, "").trim();
}
}
return filename;
}
async function getFileFromLink(url) {
const fileRes = await fetch(url);
const blob = await fileRes.blob();
let fileName = getFilenameFromContentDisposition(fileRes);
if (!fileName) {
fileName = url.split("/").pop();
}
const file = new File([blob], fileName, {
type: blob.type,
});
return file;
}
If you wanted to make this better, I'd use the content-disposition package on npm for the parsing as the formatting of it can get strange. I'd also probably use the mime package for ensuring that the filename from the URL has a proper file extension based on the returned content-type
Here's a simplest way to get blob or file object with vanila.js and promise
const fileURL_to_blob = (file_url) => {
return new Promise((resolve, reject) => {
let request = new XMLHttpRequest();
request.open('GET', file_url, true);
request.responseType = 'blob';
request.onload = function() {
var reader = new FileReader();
reader.readAsDataURL(request.response);
reader.onload = function(e){
//console.log('DataURL:', e.target.result);
resolve(e.target.result);
};
};
request.onerror=function(e){
reject(e);
}
request.send();
});
}

Categories

Resources