Wiered behaviour by Javascript - javascript

I have the following Java Script code fragment:
function upoload_TC(evt) {
var file = evt.target.files[0];
if(file.type != 'text/plain'){
evt.target.style='color:red;';
}
else{
var app_data='';
reader = new FileReader();
reader.onload = function(){
app_data = reader.result;
};
reader.readAsText(file);
if (evt.target.id[7]=='2') {
/* area of intrest begin: */
var packet_order = get_packet_order(evt.target);
console.log("1st");
var app_data1 = app_data.split('\n');
console.log("app_data: ");
console.log(app_data);
console.log("app_data1: ");
console.log(app_data1);
/* area of intrest end */
for(var i=0; i<app_data1.length; ++i){
console.log("3st");
if(app_data1[i][0] == '!'){
app_data1.splice(i,1);
--i;
console.log(app_data1);
}
console.log(app_data);
...
}
}
}
}
app_data have a long string.
After execution, sometimes app_data1 do not have any content logged.
Observation: When I execute it step by step in the debugger, app_data1 has the expected value. But if my 1st break point is after the assignment statement for app_data1, it's empty.
How can I fix this?
I found a call_back and promises to tackle this kind issues for user-defined functions. Since split() is not defined by me, I think these won't work.
I believe timeout is not the correct way to do this. Is it?
Please find the video of debugger window here.

You should be doing the logging / processing inside the onload callback. In your example code, you're setting the value for app_data on the load event, which is fired after the asynchronous function readAsText finishes its thing. By that time the logging / processing code has already been executed.

Try this:
var stringSplit = function (str) {
return new Promise(function (resolve, reject) {
var splittedString = str.split('\n');
return resolve(splittedString);
});
}
var veryLongStr = "very long string";
stringSplit(veryLongStr).then(function (splittedString) {
console.log(splittedString);
});

Related

Obtain FileReader result in JavaScript for dynamic file inputs [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 3 years ago.
I'm new to JS and I've to do a feature at work that I don't know very well how deal with it.
I have a form, where the user can add items dynamically pressing buttons. One of the buttons allows to add an input to upload a file. For symplicity I will resume the HTML to something like this:
The original div in the form it's:
<div id="userContent">
</div>
When the user press the button it adds elements, in this case file inputs:
<div id="userContent">
<div id="inputFile-1" class="customTabContent">
<input id="file-1" type="file" required="required">
</div>
<div id="inputFile-2" class="customTabContent">
<input id="file-2" type="file" required="required">
</div>
<div id="inputFile-3" class="customTabContent">
<input id="file-3" type="file" required="required">
</div>
</div>
When the user finish adding elements and press the button to submit the data, I read the DOM and to see what has created the user, first I get the parent with:
var parent = document.getElementById('userContent');
Then, I iterate over the childs and for each child I get the input:
var input = document.getElementById('file-1');
var file = input.files[0];
And then I use this function to read the file and return the value in base64, using closures to obtain the value:
function getBase64(file) {
var base64data = null;
var fileReader = new FileReader();
fileReader.onloadend = (function () {
return function () {
var rawData = fileReader.result;
/* Remove metadata */
var cleanedData = rawData.replace(/^data:(.*;base64,)?/, '');
/* Ensure padding if the input length is not divisible by 3 */
if ((cleanedData.length % 4) > 0) {
cleanedData += '='.repeat(4 - (cleanedData.length % 4));
}
base64data = cleanedData;
}
})();
fileReader.readAsDataURL(file);
return base64data;
}
My problem it's that on the website, I get an error saying that base64data it's null but if I put a breakpoint on return base64data; at the end of getBase64(file) the variable has the value on base64, and if I release the debugger I can send the value to the server.
Reading the documentation, FileReader it's asynchronous so I think that the problem seems be related to it, but how can I achieve what I want to do? I don't know why on the debugger I can get the value but outside the debugger no. I'm very stuck....
Regards.
P.D: If I don't answer it's because I'm not at work, so sorry for the delay on the response.
-------- EDIT 1
Thanks to Sjoerd de Wit I can get the result but I can't assign to a variable:
var info = {'type': '', 'data': ''};
........
} else if (it_is_a_file) {
var file = input.files[0];
var base64data = null;
getBase64(file).then((result) => {
base64data = result;
info['type'] = 'file';
info['data'] = base64data;
});
} else if
.......
return info;
But on info I get {'type': '', 'data': ''}. I'm using wrong the promise? Thanks.
-------- EDIT 2
This problem was because as a noob on JavaScript, I didn't know that using FLASK, you have to use forms and get the data in a different way.
So, the answer to this question was search how get data from a FORM with FLASK.
But I'm going to mark the answer as correct beacuse you can get the value, as I was asquing for.
You could turn the function to return a promise and then resolve the base64data when it's loaded.
function getBase64(file) {
return new Promise((resolve, reject) => {
var fileReader = new FileReader();
fileReader.onloadend = (function () {
return function () {
var rawData = fileReader.result;
/* Remove metadata */
var cleanedData = rawData.replace(/^data:(.*;base64,)?/, '');
/* Ensure padding if the input length is not divisible by 3 */
if ((cleanedData.length % 4) > 0) {
cleanedData += '='.repeat(4 - (cleanedData.length % 4));
}
resolve(cleanedData);
}
})();
fileReader.readAsDataURL(file);
});
}
Then where you want to read it you can do:
getBase64(file).then((base64data) => {
console.log(base64data);
})

Javascript testing with mocha the html5 file api?

I have simple image uploader in a website and a javascript function which uses FileReader and converts image to base64 to display it for user without uploading it to actual server.
function generateThumb(file) {
var fileReader = new FileReader();
fileReader.readAsDataURL(file);
fileReader.onload = function (e) {
// Converts the image to base64
file.dataUrl = e.target.result;
};
}
Now I am trying to write tests for this method using Mocha and Chai. My thinking is that I want to check if the file.dataUrl was successfully created and it is base64. So I would like to mock the local file somehow in testing environment (not sure how to do this). Or I should not test this at all and assume that this is working ?
The answer here depends a little. Does your "generateThumbs" method have other logic than loading a files contents and assign that to a property of a passed in object? Or does it have other logic such as generating the thumbnail from the image data, reading out file properties and assigning them to the file object? and so on?
If so then I would infact suggest you mock out the FileReader object with your own, so that you can control your test results. However, it isn't the FileReaders functionality you want to test, it is your own logic. So you should assume that FileReader works and test that your code that depends upon it works.
Now since the method you posted was a bit on the small side, In that case I would just assume it works, rename the method and work on testing the rest of your code. But there is a place for having such a mock, and I must admit it was quite fun to figure out how to mock the event target, so I will give an example here, using your method as a basis:
//This implements the EventTarget interface
//and let's us control when, where and what triggers events
//and what they return
//it takes in a spy and some fake return data
var FakeFileReader = function(spy, fakeData) {
this.listeners = {};
this.fakeData = fakeData;
this.spy = spy;
this.addEventListener('load', function () {
this.spy.loaded = true;
});
};
//Fake version of the method we depend upon
FakeFileReader.prototype.readAsDataURL = function(file){
this.spy.calledReadAsDataURL = true;
this.spy.file = file;
this.result = this.fakeData;
this.dispatchEvent({type:'load'}); //assume file is loaded, and send event
};
FakeFileReader.prototype.listeners = null;
FakeFileReader.prototype.addEventListener = function(type, callback) {
if(!(type in this.listeners)) {
this.listeners[type] = [];
}
this.listeners[type].push(callback);
};
FakeFileReader.prototype.removeEventListener = function(type, callback) {
if(!(type in this.listeners)) {
return;
}
var stack = this.listeners[type];
for(var i = 0, l = stack.length; i < l; i++) {
if(stack[i] === callback){
stack.splice(i, 1);
return this.removeEventListener(type, callback);
}
}
};
FakeFileReader.prototype.dispatchEvent = function(event) {
if(!(event.type in this.listeners)) {
return;
}
var stack = this.listeners[event.type];
event.target = this;
for(var i = 0, l = stack.length; i < l; i++) {
stack[i].call(this, event);
}
};
// Your method
function generateThumb(file, reader){
reader.readAsDataURL(file);
reader.addEventListener('load', function (e) {
file.dataUrl = base64(e.target.result);
});
}
Now we have a nice little object that behaves like a FileReader, but we control it's behavior, and we can now use our method and inject it into, thus enabling us to use this mock for testing and the real thing for production.
So we can now write nice unit tests to test this out:
I assume you have mocha and chai setup
describe('The generateThumb function', function () {
var file = { src: 'image.file'};
var readerSpy = {};
var testData = 'TESTDATA';
var reader = new FakeFileReader(readerSpy, testData);
it('should call the readAsDataURL function when given a file name and a FileReader', function () {
generateThumb(file, reader);
expect(readerSpy.calledReadAsDataURL).to.be.true;
expect(readerSpy.loaded).to.be.true;
});
it('should load the file and convert the data to base64', function () {
var expectedData = 'VEVTVERBVEE=';
generateThumb(file, reader);
expect(readerSpy.file.src).to.equal(file.src);
expect(readerSpy.file.dataUrl).to.equal(expectedData);
});
});
Here is a working JSFiddle example:
https://jsfiddle.net/workingClassHacker/jL4xpwwv/2/
The benefits here are you can create several versions of this mock:
what happens when correct data is given?
what happens when incorrect data is given?
what happens when callback is never called?
what happens when too large of a file is put in?
what happens when a certain mime type is put in?
You can offcourse massively simplify the mock if all you need is that one event:
function FakeFileReader(spy, testdata){
return {
readAsDataURL:function (file) {
spy.file = file;
spy.calledReadAsDataURL = true;
spy.loaded = true;
this.target = {result: testdata};
this.onload(this);
}
};
}
function generateThumb(file, reader){
reader.onload = function (e) {
file.dataUrl = base64(e.target.result);
};
reader.readAsDataURL(file);
}
Which is how I would actually do this. And create several of these for different purposes.
Simple version:
https://jsfiddle.net/workingClassHacker/7g44h9fj/3/

Error when decoding base64 with buffer. First argument needs to be a number

I am working on running different encoding statements based on the URL.
My code:
var parsedUrl = url.parse(req.url, true);
var queryAsObject = parsedUrl.query;
var myString = queryAsObject["string"];
var myFunction = queryAsObject["function"];
if (myFunction == "encodeb64") {
var bufEncode = new Buffer(JSON.stringify(myString));
var myEncode = bufEncode.toString('base64');
console.log(myEncode);
}
else {
console.log("Error1");
};
if (myFunction == "decodeb64") {
// var bufDecode = new Buffer(myEncode, 'base64');
// var myDecode = bufDecode.toString('utf8');
var myDecode = new Buffer(myEncode, 'base64').toString('utf8');
console.log(myDecode);
}
else {
console.log("Error2");
};
URL used: http://127.0.0.1:8020/?string=text&function=decodeb64
The issue is that I am having is with the last if statement. If its looking for decodeb64 and the first statement is looking for encodeb64 it crashes when function=decodeb64 is in the URL. If both if statements are looking for either encodeb64 or decodeb64, it runs perfectly. It also works if function=encodeb64 is in the URL.
The error message I get is:
buffer.js:188
throw new TypeError('First argument needs to be a number, ' +
^
It points to:
var myDecode = new Buffer(myEncode, 'base64').toString('utf8');
The given number on the error is pointed to the n in new.
I have located the problem to be inside the decode if statement by moving and reversing the order on the code.
As you can see in my code notes, I have tried two different methods of decoding with no success.
The reason that it crashes I believe is that when function=decode64, the variable myEncode is not declared and initialized, as the if(myFunction=="encode64") block is not run.
So when the code tried to new Buffer(myEncode...) it would fail as myEncode is undefined.
I think you meant to code :
var myDecode = new Buffer(myString, ...)
instead

converting array buffers to string

I'm getting some weird results when converting an array buffer to a string then displaying the output in a div.
I'm getting some GPS data from the USB port in a chrome packaged app. It converts the array buffer received from the port into a string and outputs. The functions are:
var onReceiveCallback = function(info) {
if (info.connectionId == connectionId && info.data) {
$(".output").append(ab2str(info.data));
}
};
/* Interprets an ArrayBuffer as UTF-8 encoded string data. */
var ab2str = function(buf) {
var bufView = new Uint8Array(buf);
var encodedString = String.fromCharCode.apply(null, bufView);
return decodeURIComponent(escape(encodedString));
};
I have a start and stop button to obviously start and stop the reading of data from the gps device. When I start it the first time it works and outputs as expected, something like:
$GPGGA,214948.209,,,,,0,0,,,M,,M,,*41 $GPGSA,A,1,,,,,,,,,,,,,,,*1E
$GPGSV,1,1,01,07,,,33*7F
$GPRMC,214948.209,V,,,,,0.00,0.00,270814,,,N*4C
$GPGGA,214949.209,,,,,0,0,,,M,,M,,*40 $GPGSA,A,1,,,,,,,,,,,,,,,*1E
$GPGSV,1,1,01,07,,,34*78
$GPRMC,214949.209,V,,,,,0.00,0.00,270814,,,N*4D
but then when I stop it, and restart it, although I clear the output div, the output data seems to be mixing in with the previous result. Like:
$$GPGPGGGGAA,,221155115544..202099,,,,,,,,,0,0,0,0,,,,,,MM,,,,MM,,,,**4455
$$GGPPGGSSAA,,AA,,11,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,**11EE
$$GGPGPGSSVV,,11,,11,,0022,,0077,,,,,,3344,1,177,,,,,,3311**77FF
$$GGPPRRMMCC,,212155115544..220099,,VV,,,,,,,,,,00..0000,,00..0000,,227700881144,,,,,,NN*4*488
$$GPGGPGGGAA,,221155115555..220099,,,,,,,,,,00,,00,,,,,,MM,,,,MM,,,,**4444
$$GGPPGGSSAA,,AA,,11,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,**11EE
$G$GPPGGSSVV,,11,,11,,0022,,0077,,,,,,331,1,1177,,,,,,2255**77FF
$$GGPPRRMMCC,2,21155115555..220099,,VV,,,,,,,,,,00..0000,,00..0000,,227700881144,,,,,,N*N*4499
Its like a buffer or variable isnt being emptied, or something else crazy that I cant figure out. Any pointers appreciated.
edit:
this is the 'start' function which clears the output div and reconnects:
// when the start button is clicked
$( "#start" ).click(function() {
if ( deviceId == 0 ) {
console.log("Please select a device");
return;
}
else {
$(".output").empty();
serial.connect(deviceId, {bitrate: 9600}, onConnect);
}
});
I have found this technique unreliable in my own code, although I don't remember if the problem was similar to one you report:
var ab2str = function(buf) { // not reliable
var bufView = new Uint8Array(buf);
var encodedString = String.fromCharCode.apply(null, bufView);
return decodeURIComponent(escape(encodedString));
};
So, I have done it this way, with code taken from one of the Google Chrome App examples (tcpserver):
function ab2str(buf, callback) {
var bb = new Blob([new Uint8Array(buf)]);
var f = new FileReader();
f.onload = function(e) {
callback(e.target.result);
};
f.readAsText(bb);
}
Note that this version isn't an exact replacement, since it's asynchronous.
Now, starting with Chrome Version 38 (now in beta), you can do it this way:
function ab2str(buf) {
var dataView = new DataView(buf);
var decoder = new TextDecoder('utf-8');
return decoder.decode(dataView);
}
As I always run the beta and am preparing examples for a forthcoming book, I am now doing it the newest way. Give that a try and see if your problem goes away. If not, my suggestion to examine info.data is still a good one, I think.
UPDATE: I've just checked out this reverse function, which you may also find handy at some point:
function str2ab(buf) {
var encoder = new TextEncoder('utf-8');
return encoder.encode(buf).buffer;
}

Javascript: Set the order of functions

I'm writing a titanium app but I'm having an issue with the execution order of my javascript.
I have an event listener on a button. It's a reload button that clears a table, uses HTTPClient to GET a JSON array of 'appointments', saves each appointment, and refreshes a table list. The problem is I am executing the table delete first which should clear the table, then I get the appointments but when the app refreshes the datatable it's like it's doing it too soon and the new appointments haven't been saved yet because I'm getting an empty list. Now if I comment out the db.deleteAll line, each time I click reload the list is refreshed with the new (and existing) appointment data.
I need to make sure everything is done in order and only when the previous task is dfinished. So appointments.download() has to be executed AFTER db.DeleteAll and the list refresh has to be executed AFTER var allAppointments = db.All();
I think the problem is that the appointments.download() function has to make a HTTP GET call and then save the results and the other functions are not waiting until it's finished.
Here is the code:
btnReload.addEventListener('click', function(e){
var affected = db.deleteAll();
appointments.download();
var allAppointments = db.all();
Ti.API.info(allAppointments);
appointmentList.setData(allAppointments);
});
Here are the functions that are being called:
db.deleteAll():
api.deleteAll = function(){
conn.execute('DELETE FROM appointments');
return conn.rowsAffected;
}
appointments.download():
var appointments = (function() {
var api = {};
api.download = function(){
var xhr = Titanium.Network.createHTTPClient();
xhr.onload = function()
{
var data = JSON.parse(this.responseText);
var dl = (data.length);
for(i=0; i<dl;i++)
{
//p = addRow(data,i); // returns the **arr array
//Ti.API.info('Saving : '+data[i].first_name);
var contact_name = data[i].first_name + ' ' + data[i].last_name;
var start_date = data[i].start_date;
var reference = data[i].reference;
var comment = data[i].comment;
var appointment_id = data[i].quote_id;
var lastid = db.create(appointment_id, start_date, reference, contact_name, comment);
//Ti.API.info(lastid);
}
};
xhr.open('GET','http://********.co.uk/appointments/download/');
xhr.send();
return;
}
Any help most appreciated!
Billy
Synchronous calls give you coordination (code won't execute until any computation it depends on finishes) for free. With asynchronous calls, you have to take care of coordination. This generally means passing the dependent code as a function to the asynchronous code. The passed code is known as a "continuation", which means "the rest of the calculation, from a given point forward". Passing continuations around is known as (unsurprisingly) "continuation passing style".
To rewrite code in CPS, identify the point(s) where you need to coordinate the code (the call to appointments.download), then wrap the rest of the code in a function.
btnReload.addEventListener('click', function(e){
var affected = db.deleteAll();
appointments.download();
function () {
var allAppointments = db.all();
Ti.API.info(allAppointments);
appointmentList.setData(allAppointments);
}
});
In the general case, the return value becomes the argument to the continuation. Here, no return value for appointments.download is used, so the continuation takes no arguments.
Next, rewrite the asynchronous function to take the continuation and pass the continuation in the call.
btnReload.addEventListener('click', function(e){
var affected = db.deleteAll();
appointments.download(
function () {
var allAppointments = db.all();
Ti.API.info(allAppointments);
appointmentList.setData(allAppointments);
});
});
...
api.download = function(_return){
var xhr = Titanium.Network.createHTTPClient();
xhr.onload = function() {
var data = JSON.parse(this.responseText);
var dl = (data.length);
for (i=0; i<dl;i++) {
//p = addRow(data,i); // returns the **arr array
//Ti.API.info('Saving : '+data[i].first_name);
var contact_name = data[i].first_name + ' ' + data[i].last_name;
var start_date = data[i].start_date;
var reference = data[i].reference;
var comment = data[i].comment;
var appointment_id = data[i].quote_id;
var lastid = db.create(appointment_id, start_date, reference, contact_name, comment);
//Ti.API.info(lastid);
}
_return();
};
xhr.open('GET','http://********.co.uk/appointments/download/');
xhr.send();
return;
}
The continuation is named _return because the return statement can be modeled as a continuation (the default continuation). Calling _return in the asynchronous version would have the same affect as calling return in the synchronous version.
Currently you are making requests asynchronously which means you make a request and return from the function immediately, you don't wait for an answer. You should make your calls synchronous, I don't know what your conn and xhr really are but they might provide ways to make the execute() and send() methods synchronous. For example if you set the third argument of JavaScript's own XMLHttpRequest's open() method to false then send() method will not return until a response is received from the server, your connection classes might have the same option.
Move the call to delete the current appointments into the onload handler. That way you will delete the old and immediately add the new data.

Categories

Resources