I have the following function in the client-side of a web app:
function fetchDataFromApi(fetchCode, options, callback) {
var dataObject = JSON;
dataObject.fetchCode = fetchCode;
dataObject.options = options;
var xhr = new XMLHttpRequest();
var url = "DATA_API_URL";
// connect to the API
xhr.open("POST", url, true);
xhr.setRequestHeader("Content-Type",
"application/json"
);
// set callback for when API responds. This will be called once the request is answered by the API.
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
// API has responded;
var json = {
ok: false,
message: 'could not parse response'
};
try {
// parse the raw response into the API response object
json = JSON.parse(xhr.responseText);
} catch (err) {
// probably json parse error; show raw response and error message
console.log(err);
console.log("raw response: " + xhr.responseText);
}
if (json.ok) {
// success, execute callback with argument json.data
callback(json.data);
} else {
// fetch failed;
console.error(json.message);
}
}
};
// send request payload to API
var data = JSON.stringify(dataObject);
xhr.send(data);
}
Since I am using an asynchronous call (the third parameter in xhr.open is set to true), I am surprised to find that this function blocks the UI in the browser. When there is a substantial amount of data grabbed from the server with this function, it can take 3-4 seconds, blocking the UI and generating this error in the Chrome console:
[Violation] 'load' handler took 3340ms
This function is currently in production here, where I am calling the function as so:
function getNamesFromApi() {
fetchDataFromApi('chj-confraternity-list', {}, function (data) {
fadeReplace(document.getElementById('spinner-2'), document.getElementById(
'name-list-container'),
false, true);
// transaction was successful; display names
var listString = "";
if (data.list) {
// add the names to the page
var listLength = data.list.length;
for (var x = 0; x < listLength; x++) {
document.getElementById('name-list-container').innerHTML +=
"<div class='name-list-item'>" +
"<span class='name-list-name'>" +
data.list[x].name +
"</span>" +
"<span class='name-list-location'>" +
data.list[x].location +
"</span>" +
"</div>";
}
}
});
}
window.addEventListener('load', function() {
getNamesFromApi();
});
Why is this blocking the UI, and what am I doing wrong in making an asynchronous XMLHttpRequest?
UPDATE: Thanks to the comments for pointing me in the right direction; the issue was not the XMLHttpRequest, but rather me appending innerHTMl within a loop. The issue is now fixed, with the corrected snippet in the answer.
The UI was blocked because i was appending innerHTML within a loop, an expensive, and UI-blocking operation. The issue is now fixed. Here is the corrected snippet:
function getNamesFromApi() {
fetchDataFromApi('chj-confraternity-list', {}, function (data) {
fadeReplace(document.getElementById('spinner-2'), document.getElementById(
'name-list-container'),
false, true);
// transaction was successful; display names
if (data.list) {
var listString = "";
// add the names to the page
var listLength = data.list.length;
for (var x = 0; x < listLength; x++) {
listString +=
"<div class='name-list-item'>" +
"<span class='name-list-name'>" +
data.list[x].name +
"</span>" +
"<span class='name-list-location'>" +
data.list[x].location +
"</span>" +
"</div>";
}
document.getElementById('name-list-container').innerHTML = listString;
}
});
}
window.addEventListener('load', function() {
getNamesFromApi();
});
Here is my code:
'use strict';
var unitID = 0;
var getById = function(generalOptions, specificOptions) {
describe('API tests for: ' + specificOptions.name, function() {
var url = generalOptions.baseUrl + specificOptions.route;
// GET all items
it('= = = GET ALL test for ' + specificOptions.name + ' return status
code 200', function(done) {
generalOptions.request.get({
url: url
}, function(error, response, body) {
expect(response.statusCode).toBe(200);
expect(JSON.parse(body)).not.toBeFalsy();
if (specificOptions.route == '/devices/') {
var bodyJS = JSON.parse(body);
unitID = bodyJS.devices[0].id;
} else {
unitID = '';
}
console.log('Result 1 - ' + unitID);
done();
});
});
//GET by ID
it('= = = GET by ID test for ' + specificOptions.name + ' return status code 200', function(done) {
console.log('Result 2 - ' + unitID);
generalOptions.request.get({
url: url + unitID
}, function(error, response, body) {
expect(response.statusCode).toBe(200);
expect(JSON.parse(body)).not.toBeFalsy();
done();
});
});
})
};
module.exports = getById;
I need to wait, while unitID will be updated with first GET request and then use in in the next request.
The problem is, that it works asynchronously and unitID in the second request stay 0.
Can show how to implement solution with async/await or Promises?
Thanks!
For debugging reason I do console.log. For now it print:
Result 2 - 0
Result 1 - 59dffdgfdgfg45545g
You should not write test in such fashion where output of one test goes into other.Each "it" should be independent.
Instead you should make call twice(nested call) to achieve the value of unitID or ideally you should mock the service to return the data that is expected by the "it".
This function works within typeahead.js context,
i use lodash request-promise and cheerio
to grab/parse/organize my data.
console.log shows every variables as it should be.
but with this line :
return '<p>' + img + data.name + ' - ' + getProviders(data) + '</p>'
i get the following error:
Uncaught TypeError: Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'.
I'm not sure what's happening, this might have something to do with typeahead.js trying to happend an invalid (because incomplete yet) DOM element.
PS: let img = $(selector).attr('src'); is intended (it stores the src attribute as a string)
PS2: if i ommit img it works:
return '<p>' + data.name + ' - ' + getProviders(data) + </p>'
function(data) {
let imgProviders = _.keys(data.url).map((r) => {
return {[r]: data.url[r]};
});
let url = imgProviders[0][_.keys(imgProviders[0])[0]].replace(/\/$/, '');
let selector = _.find(sites, {
providerName: _.keys(imgProviders[0])[0]
}).coverIMG;
let body = request.get(url, (err, res, html) => {
$ = cheerio.load(html);
// the variable that is messing with me
let img = $(selector).attr('src');
// works fine when console.log()
console.log(img, data.name, getProviders(data));
return '<p>' + img + data.name + ' - ' + getProviders(data) + '</p>';
});
}
I need a way to replace part of a string with another string using Google Apps Script. I tested the following and it worked:
function test(){
var value = 'https://plus.google.com/117520727894266469000';
var googleimageURL = googlePlus(value);
Logger.log('Returned Result: ' + googleimageURL);
}
function googlePlus(value){
var apiKey = ScriptProperties.getProperty('apiKey');
var re = 'http://'
var theURL = value;
Logger.log('Google+ is called: ' + value);
var replacingItem = 'https://';
var theURL = theURL.replace(re, replacingItem);
Logger.log('Google+ Values 2: ' + value + ' : ' + theURL + ' after ' + replacingItem);
return imageURL;
}
But, when I embedded into the following code, it did not work. Any idea?
//If a Google+ column was not specified,put a flag.
if (googleColumn && column == googleColumn) {
if (value == ""){
columns.push(nogoogleColumn);
values.push(flag);
var googleimageURL="";
} else {
var googleimageURL = googlePlus(value);
Logger.log('Returned Result: ' + googleimageURL);
}
}
The script did not work as I wish. It seems to stop at line:
var theURL = theURL.replace(re, replacingItem);
Additional information: Google notified me with the following message
onFormSubmit
TypeError: Cannot find function replace in object https://plus.google.com/117520727894266469000. (line 536) formSubmit
I found the mistake. It is Type Error. value in the second block is not a "string", while the first block is a "string". Hence to fix the second block, I need to use:
var value = value.toString();
before passing to googlePlus(value)
Im having a strange problem with the following code:
function getTrxData(trx,inputPar,outputPar,callback) {
var retorno = {};
var URL = '/XMII/Runner?Transaction=' + trx;
var params = "";
for(key in inputPar)
params = params + "&" + key + "=" + inputPar[key];
if(!outputPar)
outputPar = "*";
if(params)
URL = URL + params;
URL = URL + '&OutputParameter=' + outputPar;
$.ajax({
type: "GET",
url: URL,
async: true,
success: function(data){
retorno.datos = $.xml2json(data);
retorno.tipo = 'S'; // Success
retorno.mensaje = "Datos obtenidos correctamente";
callback(retorno);
},
error: function(jqXHR, textStatus, errorThrown){
retorno.tipo = 'E'; // Error
retorno.mensaje = "Error: " + textStatus;
callback(retorno);
}
});
}
function crearSelect(trx,inputPar,outputPar,selectID,campoTextoXX,campoValor,valorDefault,callback2) {
// At this point campoTextoXX exists and has a value
getTrxData(trx,inputPar,outputPar,function(retorno2) {
// At this point campoTextoXX is an object equal to callback2
if(retorno2.tipo == 'E') {
callback2(retorno2);
return false;
}
var options = "";
var selected = "";
$.each(retorno2.datos.Rowset.Row, function(k,v) {
if(valorDefault == v[campoValor]) {
selected = " selected='selected'";
} else {
selected = "";
}
options = options + "<option value='" + v[campoValor] + selected "'>";
options = options + v[campoTextoXX];
options = options + "</option>";
});
$("#" + selectID + " > option").remove();
$("#" + selectID).append(options);
callback2(retorno2);
});
}
And the call is like this:
crearSelect("Default/pruebas_frarv01/trxTest",{letra: 'V'},"*",'selectID',"CustomerID",'OrderID','',function(retorno) {
alert(retorno.tipo + ": " + retorno.mensaje);
});
The problem is that campoTextoXX and campoValor dont get any value inside the callback function. Also, debugging in Chrome shows me that campoTextoXX has the value of the callers callback function:
alert(retorno.tipo + ": " + retorno.mensaje);
I dont know what to do next.
Any ideas?
Thx
You might find it easier to mange the callback chain by exploiting $.ajax's ability to behave as a jQuery Deferred.
This allows us very simply to specify the "success" and "error" behaviour in the guise of request.done(...) and request.fail(...) at the point where getTrxData is called rather than inside getTrxData - hence the callback chain is (ostensibly) one level less deep.
function getTrxData(trx, inputPar, outputPar) {
inputPar.Transaction = trx;
inputPar.OutputParameter = (outputPar || '*');
return $.ajax({
url: '/XMII/Runner?' + $.param(inputPar)
});
}
function makeOptions(obj, selectID, campoTextoXX, campoValor, valorDefault) {
var $option, selected, $select = $("#" + selectID);
$("#" + selectID + " > option").remove();
$.each(obj.datos.Rowset.Row, function(k, v) {
selected = (valorDefault == v[campoValor]) ? ' selected="selected"' : '';
$option = $('<option value="' + v[campoValor] + selected + '">' + v[campoTextoXX] + "</option>");
$select.append($option);
});
return obj;
}
function crearSelect(trx, inputPar, outputPar, selectID, campoTextoXX, campoValor, valorDefault, callback) {
var request = getTrxData(trx, inputPar, outputPar);
request.done(function(data) {
var obj = {
datos: $.xml2json(data),
tipo: 'S',// Success
mensaje: "Datos obtenidos correctamente"
};
callback(makeOptions(obj, selectID, campoTextoXX, campoValor, valorDefault));
});
request.fail(function(jqXHR, textStatus, errorThrown) {
var obj = {
tipo: 'E',// Error
mensaje: "Error: " + textStatus
};
callback(obj);
});
}
crearSelect("Default/pruebas_frarv01/trxTest", {letra:'V'}, "*", 'selectID', "CustomerID", 'OrderID', '', function(retorno) {
alert(retorno.tipo + ": " + retorno.mensaje);
});
You will see that this is essentially a refactored version of your original code, with significant simplification of the string handling in getTrxData, which appears to work correctly.
The options code has been pulled out as a separate function, makeOptions, to make the new structure of crearSelect clearer. This is not strictly necessary and the code could be re-combined without penalty.
Tested here insomuch as to make sure it loads and runs through to the "Error" alert, which it does successfully. Without access to the server-side script, I can't test/debug the full ajax functionality so you may need to do some debugging.
The problem appears to be that you are overwriting the variable "pepe" somewhere in your code.
Also, check how you are assigning your callback function and parameter object. A quick look appears that it is not being supplied the correct parameters.
You should be careful not to use global variables within your success and error functions. so instead of:
success: function(data){
retorno.datos = $.xml2json(data);
retorno.tipo = 'S'; // Success
retorno.mensaje = "Datos obtenidos correctamente";
callback(retorno);
}
I think you should do something like:
success: function(data){
var retorno = {};
retorno.datos = $.xml2json(data);
retorno.tipo = 'S'; // Success
retorno.mensaje = "Datos obtenidos correctamente";
callback(retorno);
}
furthermore you should use Firebug for Firefox to step through your code and watch your variables to ensure that the data is coming in correctly, and not getting overwritten at any point
Your control flow is a bit confusing, and another thing you can do is check to make sure your callbacks and variables are correct using some typeof conditionals to make sure they are functions, etc. try doing things like this:
success: function(data){
var retorno = {};
retorno.datos = $.xml2json(data);
retorno.tipo = 'S'; // Success
retorno.mensaje = "Datos obtenidos correctamente";
if (typeof callback !== "function" || typeof data !== "object"){
console.log('error');
throw "callback or data is not correct type";
}
callback(retorno);
}
and make sure you aren't getting an error in the console.