JS - Method executes last even though it's invoked first - javascript

I have these two functions, where "form" is the name of the Vue object:
form.sizeChartAsImage();
form.setSizeChart();
This is the code of said functions:
setSizeChart: function () {
for (i = 0; i < this.columns.length; i++) {
this.product.size_chart.push({
position_x: 0,
position_y: i,
value: this.columns[i],
})
for (j = 0; j < this.data.length; j++) {
for (var key in this.data[j]) {
if(key === this.columns[i]) {
this.product.size_chart.push({
position_x: j+1,
position_y: i,
value: this.data[j][key],
})
}
}
}
}
}
sizeChartAsImage: function() {
html2canvas($("#size-chart").get(0)).then(canvas => {
canvas.toBlob (blob => {
var sizechartImg = document.createElement('img');
url = URL.createObjectURL(blob);
sizechartImg.src = url;
this.product.size_chart_image = sizechartImg;
debugger;
}, 'image/png');
})
}
Nevertheless, the second function get executed first (debugger enters first) and then the rest of the code runs; the form is submitted, and lastly, sizeChartAsImage() gets executed, causing no effect (since the form was submitted with "size_chart_image" as null)
This is what I'm trying to render, and it does generate the image.
<demo-grid
:data="data"
:columns="columns"
id="size-chart">
</demo-grid>
Could it be because it's a Vue component? Performance issues? Or do I need to use a callback maybe?

It's because html2canvas($("#size-chart").get(0)) is returning a promise (maybe just a thenable), which is an async call.
So sizeChartAsImage will run, and html2canvas($("#size-chart").get(0)) will execute. While the script is waiting for that to return it'll continue to setSizeChart function and run it. And then it'll return to the code within the then(canvas => callback.
You could either call setSizeChart at the end of the callback. Or, if you're using ES2017 or greater, you could re-write sizeChartAsImage to be async and await it.

Related

Vue Js While inside the function I called with Axios get, printing the incoming data into the array

Inside the function in the screenshot, I am adding the data from the backend function to the array with axios.get. But when I go outside of axios, the values of the array I print are undefined.
I am getting string value from backend. I want to be able to use it in different methods by returning it. Please help me. I can't find the solution.
getReasonsForWaitingCustomer() {
this.adimKodlariLastString = "";
if (this.$route.params.status == "musteri-bekleniyor") {
axios.get(URL + "xxx/xxx?xxx=xxx)
.then(response => {
for (var i = 0; i < response.data.data.length; i++) {
if (this.stepCode.includes(response.data.data[i].adim_kodu) == false) {
this.stepCode.push(response.data.data[i].adim_kodu);
}
}
for (var j = 0; j < this.stepCode.length; j++) {
this.adimKodlari += this.stepCode[j] + ",";
}
this.adimKodlariLastString = this.adimKodlari.slice(0, -1);
console.log("inAxiosThen",this.adimKodlariLastString);
})
}
console.log("afterAxios",this.adimKodlariLastString);
return "apfapofapkapfka" --> It's working
return this.adimKodlariLastString --> It's not working. I want this to work.
},
In the solution examples I reviewed, the incoming value was used in the html tags. But I want to be able to use incoming value in methods.
When the string values I want to use are in .then(response), I can get them when I press the console.
When I press the console other than .then() I don't get the values.
You won't get any data this way because axios calls are asynchronous. The easiest and cleaner way is:
const response = await axios.get('url')
console.log(response.data)
this.adimKodlariLastString = response.data
// and then you could do your logic
Alternatively, to have it working with your code, you could just add:
await
before axios (you could do it with pure Promises but you'll end up getting Promise inside Promise).
Current,this is my function's last status. This is working. Return is successfull.
async getReasonsForWaitingCustomer() {
let adimKodlariLastString = "";
let stepCode = [];
let adimKodlari = "";
if (this.$route.params.status == "xxx") {
const response = await axios.get(URL + "xxx/xxx");
for (var i = 0; i < response.data.data.length; i++) {
if (stepCode.includes(response.data.data[i].adim_kodu) == false) {
stepCode.push(response.data.data[i].adim_kodu);
}
}
for (var j = 0; j < stepCode.length; j++) {
adimKodlari += stepCode[j] + ",";
}
adimKodlariLastString = adimKodlari.slice(0, -1);
}
console.log("adimKodu",adimKodlariLastString);
return adimKodlariLastString;
},
Promise in console
I looked at their solutions, but I didn't understand anything. In the solutions they manually assigned a value and called the result. I have no idea how to get my incoming data in PromiseResult.
Good news. I founded solution of this problem. I hope it will be useful for those who are looking for a solution. As I mentioned, I said that I wanted to use the value that came as a prompt in another function.
I'm successfully called my values in this function
I'm converted this function the async. I brought the first function as await.
Last result in the console

how to set default value in case of error

I'm learning React and I'm trying to render 10 pieces of data from a specific API.
This is a function I wrote for iterating through the fetched data and taking the title and the image:
for (let i = 0; i < 10; i++) {
data.push({
title: someMethod(allData)[i].children[0].data,
image: someMethod(allData)[i].attribs.src,
});
}
I don't know why but one of the images gives me that error:
index.js:1 TypeError: Cannot read property 'attribs' of undefined
Which stops the whole rendering.
I wanted to try to put a temporary placeholder image so the rest of the images can be loaded without any errors.
I thought about adding conditions into the image line inside the loop but it didn't work for me. What's the right way to do that?
If I wasn't clear enough please comment what wasn't clear and I'll try to describe it better.
If someMethod is synchronous (based on your code sample it should be) then you can wrap your code in try...catch block like so:
for (let i = 0; i < 10; i++) {
try {
data.push({
title: someMethod(allData)[i].children[0].data,
image: someMethod(allData)[i].attribs.src,
});
} catch (err) {
data.push({
title: 'placeholder',
image: 'placeholder',
});
continue;
}
}
Here's more cleaner version of the above:
for (let i = 0; i < 10; i++) {
let title = '';
let image = '';
try {
title = someMethod(allData)[i].children[0].data;
image = someMethod(allData)[i].attribs.src;
} catch (err) {
image = 'failed to load or something';
}
data.push({title, image});
}
for (let i = 0; i < 10; i++) {
data.push({
title: someMethod(allData)[i].children[0].data,
image: someMethod(allData)[i].attribs?.src ?? defaultSrc,
});
}
docs: Optional_chaining and Nullish_coalescing_operator
You can solve the null pointer exceptions in 2 ways.
Manually check for truthy values using ? or &&
Create a simple utility function
Point 1 will solve your problem, but however if you start using it everywhere throughout your code it may not look tidy. I'd recommend to go by creating a utility function as below.
export const getSafe = (fn, defaultValue=null) => {
try {
return fn()
} catch (e) {
return defaultValue
}
}
You can then use it anywhere like..
var result = getSafe(() => object1.data.key)
You can use it for accessing any type of values to any depth. You can also pass a default value as 2nd argument if required. by default it is set as null
var resultArray = getSafe(() => array1.data.values, [])
Just remember, the 1st value should be passed as a function.
So in your case...
for (let i = 0; i < 10; i++) {
data.push({
title: getSafe(() => someMethod(allData)[i].children[0].data, "Default Title"),
image: getSafe(() => someMethod(allData)[i].attribs.src, "placehoder_img"),
});
}
Implement the map function.
The for loop will give an error at your component
In react if you need to render elements don't implement the for loop
datas.map(......)

Protractor: Can you delay the WebElement.sendKeys() globally onPrepare?

I am runing protractor on a slow machine and I need protractor to slow down each key press and each action. The action part is done, but how can I do the keyPress part?
I have a local solution with is:
function delay(el, value, newDelay) {
for (var i = 0; i < value.length; i++) {
browser.sleep(newDelay || browser.params.delay);
el.sendKeys(value[i]);
}
}
In onPrepare I was able to slow down each action with:
browser.driver.controlFlow().execute = function () {
var args = arguments;
if (arguments[1] === "WebElement.sendKeys()")
debugger;
origFn.call(browser.driver.controlFlow(), function () {
return protractor.promise.delayed(100);
});
return origFn.apply(browser.driver.controlFlow(), args);
};
but I don't know how to slow down the sendKeys, I belive I have to do something where I placed the debugger, but what?
Aparenty, the only solution I found, was to try and send at first the entire string and if it fails, send the keys one by one, and check again, so my code is something like this:
el.getAttribute('value').then(function (insertedValue) {
if (insertedValue !== value) {
el.clear().then(function () {
el.sendKeys(protractor.Key.END);
for (var i = 0; i < value.length; i++) {
browser.sleep(100);
el.sendKeys(value[i]);
el.sendKeys(protractor.Key.END);
}
if (tryNo < 1) {
el.getAttribute('value').then(function (insertedValue) {
if (insertedValue !== value) {
.......................
}
});
}
});
}
});

IE8 long running script error when using DataTables

I have an application that uses the DataTables jQuery library to render content in my target browser IE8. The problem is when I push a big array to be rendered, IE8 sometimes throws up the infamous long running script error.
After profiling the app it seems that the call to __fnAddData in the following code is causing the problem:
if (bUsePassedData) {
for (var i = 0, len = oInit.aaData.length; i < len; i++) {
_fnAddData(oSettings, oInit.aaData[i]);
}
} else if (oSettings.bDeferLoading ||
(oSettings.sAjaxSource === null && oSettings.ajax === null)) {
_fnAddTr(oSettings, $(oSettings.nTBody).children('tr'));
}
I was looking around for solutions and saw Nicholas Zakas' write up here and tons of other solutions that would work if the for loop wasn't inside of an if else if "block". When I tried, on my 1st attempt of many, to wrap it in a setTimeout function it of course didn't work because the 2nd part of the if else if resolves to true.
(oSettings.sAjaxSource === null && oSettings.ajax === null) // true
What is a good solution for this? Thanks in advance.
I think you might split up your function in 3 functions:
Before the if statement.
Processing the oInit.aaData
After the if statement
Here is the code split up in 3 functions:
function beforeIf(){
if (bUsePassedData) {
procesData(oSettings,oInit.aaData.concat());
} else if (oSettings.bDeferLoading ||
(oSettings.sAjaxSource === null && oSettings.ajax === null)) {
_fnAddTr(oSettings, $(oSettings.nTBody).children('tr'));
}
afterIF();
}
function processData(oSettings,arr){
//process in chuncks of 50;
// setTimeout takes a long time in IE
// it'll noticibly slow donw your script when
// only processing one item at the time
var tmp=arr.splice(0,50);
for (var i = 0, len = tmp.length; i < len; i++) {
_fnAddData(oSettings, tmp[i]);
}
if(arr.length!==0){
setTimeout(function(){
processData(oSettings,arr);
},0);
return;
}
afterIf();
}
function afterIf(){
//continue processing
}
Thanks #HMR. You helped to bring me closer to my goal. To solve the problem I worked my code down to this IIFE:
(function processData(oSettings, arr) {
var tmp = arr.splice(0, 50);
tickApp.$orders.dataTable().fnAddData(tmp);
if (arr.length !== 0) {
setTimeout(function () {
processData(oSettings, arr);
}, 0);
}
}(oSettings, oInit.aaData.concat()));
Instead of using the private _fnAddData function I opted for the DataTables public fnAddData (http://datatables.net/ref#fnAddData) function. This way I am able to push 50 rows at a time into the table which is stored in the tickApp.$orders object which I just a reference to my jQuery object that stores the table in memory:
tickApp.$orders = $('#orders');
In another part of my code. They way you had it it was still pushing 1 row at a time instead of the whole 50.
Thanks again.
If you are using ajax to fetch your data, you can override "fnServerData" in your datatables config object. This will allow you to fetch the data to be loaded and then process it however you want.
In my case, I have a generic datatables config object that I use for all my datatables. I override the default fnServerData function with one that passes rows to the datatable in sets of 200 using fnAddData and setTimeout to call the function again until all the data has been processed, finally I call fnDraw to draw the table.
var DEFAULT_CHUNK_SIZE = 200;
function feedDataToDataTableInChunks(startIndex, data, oSettings) {
var chunk = data.slice(startIndex, DEFAULT_CHUNK_SIZE);
oSettings.oInstance.fnAddData(chunk, false);
if((startIndex += DEFAULT_CHUNK_SIZE) < data.length) {
setTimeout(function () {
feedDataToDataTableInChunks(startIndex, data, oSettings);
});
} else {
oSettings.oApi._fnInitComplete(oSettings, data);
oSettings.oInstance.fnDraw();
}
}
var config = {fnServerData: function(){
oSettings.jqXHR = $.getJSON(sSource, aoData)
.done(function (result) {
feedDataToDataTableInChunks(0, result || [], oSettings);
});
}}
I am using datatables version 1.9.4

Load dictionary file with ajax and don't crash iPhone Mobile Safari

I have a web application where I load (via ajax) a dictionary file (1MB) into the javascript array. I found the reason why the Mobile Safari crashes after 10 seconds. But now what I'm wondering is how do I get around this issue?
On the link above the answer suggest using setInterval, but this would mean I would have to have a dictionary file chunked into pieces and have them loaded one by one. This surely could be done, but I would have to make a lot of chunks taking into account the internet speed and too many requests would take forever for the page to load (and if I make the chunks too big it could happen that some mobile users wouldn't be able to download the chunk in a given 10second period).
So, my question is: has anyone encountered this kind of problem and how did you go about it? A general push in the right direction is appreciated.
edit:
This is the js code which I use to load the dictionary:
var dict = new Trie();
$.ajax({
url: 'data/dictionary_342k_uppercase.txt',
async: true,
success: function (data) {
var words = data.split('\n');
for (var i = words.length - 1; i >= 0; i--) {
dict.insert(words[i]);
}
},
error: function(){
$('#loading-message').text("Problem s rječnikom");
}
});
Trie.js:
function Trie () {
var ALPHABET_SIZE = 30;
var ASCII_OFFSET = 'A'.charCodeAt();
this.children = null;
this.isEndOfWord = false;
this.contains = function (str) {
var curNode = this;
for (var i = 0; i < str.length; i++) {
var idx = str.charCodeAt(i) - ASCII_OFFSET;
if (curNode.children && curNode.children[idx]) {
curNode = curNode.children[idx];
} else {
return false;
}
}
return curNode.isEndOfWord;
}
this.has = function (ch) {
if (this.children) {
return this.children[ch.charCodeAt() - ASCII_OFFSET] != undefined;
}
return false;
}
this.next = function (ch) {
if (this.children) {
return this.children[ch.charCodeAt() - ASCII_OFFSET];
}
return undefined;
}
this.insert = function (str) {
var curNode = this;
for (var i = 0; i < str.length; i++) {
var idx = str.charCodeAt(i) - ASCII_OFFSET;
if (curNode.children == null) {
curNode.children = new Array(ALPHABET_SIZE);
curNode = curNode.children[idx] = new Trie();
} else if (curNode.children[idx]) {
curNode = curNode.children[idx];
} else {
curNode = curNode.children[idx] = new Trie();
}
}
curNode.isEndOfWord = true;
return curNode;
}
}
This is a very common issue once you start doing processing in JS. If the Mobile Safari issue is the cause then what you want to do is figure out where the CPU time is going here.
I'm assuming it's the dict.insert() loop and not the data.split() call (that would be a bit more difficult to manage).
The idea here is to split up the dict.insert() loop into functional blocks that can be called asynchronously in a sequenced loop (which is what the setupBuildActions function does). After the first block each subsequent block is called via setTimeout, which effectively resets the function-time counter in the JS runtime (which seems to be what's killing your process).
Using the Sequencer function means you also keep control of the order in which the functions are run (they always run in the sequence they are generated in here and no two or more functions are scheduled for execution at the same time). This is much more effective than firing off thousands of setTimeout calls without callbacks. Your code retains control over the order of execution (which also means you can make changes during execution) and the JS runtime isn't overloaded with scheduled execution requests.
You might also want to check the node project at https://github.com/michiel/sequencer-js for more sequencing examples and http://ejohn.org/blog/how-javascript-timers-work/ for an explanation on setTimeout on different platforms.
var dict = new Trie();
// These vars are accessible from all the other functions we're setting up and
// running here
var BLOCKSIZE = 500;
var words = [];
var buildActions = [];
function Sequencer(funcs) {
(function() {
if (funcs.length !== 0) {
funcs.shift()(arguments.callee);
}
})();
}
// Build an Array with functions that can be called async (using setTimeout)
function setupBuildActions() {
for (var offset=0; offset<words.length; offset+= BLOCKSIZE) {
buildActions.push((function(offset) {
return function(callback) {
for (var i=offset; i < offset + BLOCKSIZE ; i++) {
if (words[i] !== null) { // ugly check for code brevity
dict.insert(words[i]);
}
}
// This releases control before running the next dict.insert loop
setTimeout(callback, 0);
};
})(offset));
}
}
$.ajax({
url: 'data/dictionary_342k_uppercase.txt',
async: true,
success: function (data) {
// You might want to split and setup these calls
// in a setTimeout if the problem persists and you need to narrow it down
words = data.split('\n');
setupBuildActions();
new Sequencer(buildActions);
},
error: function(){
$('#loading-message').text("Problem s rječnikom");
}
});
Here's an example using setTimeout to defer the actual insertion of words into your trie. It breaks up the original string into batches, and uses setTimeout to defer processing of inserting each batch of words. The batch size in my example is 5 words.
The actual batch insertion happens as subsequent event handlers in the browser.
It's possible that just breaking the words up into batches might take too long. If you hit this problem, remember you can chain setTimeout() calls, eg iterating for a while then using setTimeout to schedule another event to iterate over some more, then setTimeout again, etc.
function addBatch(batch)
{
console.log("Processing batch:");
for (var i = 0; i < batch.length; i++)
console.log(batch[i]);
console.log("Return from processing batch");
}
var str = "alpha\nbravo\ncharlie\ndelta\necho\nfoxtrot\n" +
"golf\nhotel\nindia\njuliet\nkilo\nlima\n" +
"mike\nnovember\noscar\npapa\nquebec\n" +
"romeo\nsierra\ntango\nuniform\n" +
"victor\nwhiskey\nxray\nyankee\nzulu";
var batch = []
var wordend;
for (var wordstart = 0; wordstart < str.length; wordstart = wordend+1)
{
wordend = str.indexOf("\n", wordstart);
if (wordend < 0)
wordend = str.length;
var word = str.substring(wordstart, wordend);
batch.push(word);
if (batch.length > 5)
{
setTimeout(addBatch, 0, batch);
batch = [ ];
}
}
setTimeout(addBatch, 0, batch);
batch = [ ];

Categories

Resources