This question already has answers here:
How to wait until jQuery ajax request finishes in a loop?
(5 answers)
How do I return the response from an asynchronous call?
(41 answers)
Closed 7 years ago.
I am attempting to populate an array and use it later. I am getting "undefined" result when I try accessing objects at indexes.
$(document).ready(function() {
var streamers = ["freecodecamp", "GeoffStorbeck", "terakilobyte"];
var cb = '?client_id=5j0r5b7qb7kro03fvka3o8kbq262wwm&callback=?';
var url = 'https://api.twitch.tv/kraken/';
var result = {};
streamers.forEach(function(stream)
{
$.getJSON(url + 'streams/' + stream + cb).success(function(data)
{
var streaming = (data.stream === null) ? false : true;
result.push(stream + " - " + streaming);
});
});
alert(result[0]);
alert(result[1]);
alert(result[2]);
});
What you need is a callback, as your getting something for the server mostly happens in asynchronous processes, which mean that the code continues executing and only adds something to the array when something is returned. You want the script to only alert things when it completed a task, which is why we call it a call back, as it will call the passed function back (aka when it's done).
$(document).ready(function() {
var streamers = ["freecodecamp", "GeoffStorbeck", "terakilobyte"];
var cb = '?client_id=5j0r5b7qb7kro03fvka3o8kbq262wwm&callback=?';
var url = 'https://api.twitch.tv/kraken/';
// This should be initialised as an array, not an object!
var result = [];
function callback(){
// I add this line to reduce the amount of alerts
if(result.length !== 3) return;
alert(result[0]);
alert(result[1]);
alert(result[2]);
}
streamers.forEach(function(stream){
$.getJSON(url + 'streams/' + stream + cb).success(function(data){
var streaming = (data.stream === null) ? false : true;
result.push(stream + " - " + streaming);
callback();
});
});
});
You are accessing result before the $.getJSON callbacks have been executed. Many questions like this have been answered already, see Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference for an explanation of the problem.
You should make use of the fact that all Ajax methods return promises/deferred objects and use $.when to wait for all of them to complete:
var deferreds = streamers.map(function(stream) {
return $.getJSON(url + 'streams/' + stream + cb).then(function(data) {
var streaming = (data.stream === null) ? false : true;
return stream + " - " + streaming;
});
});
$.when.apply(null, deferreds).then(function(result) {
console.log(result);
});
$.getJSON is asynchronous.
Your alerts are running before you have data in the array.
To view the data when it's available, move the alerts (or better yet, console.logs) up into the success callback:
$(document).ready(function() {
var streamers = ["freecodecamp", "GeoffStorbeck", "terakilobyte"];
var cb = '?client_id=5j0r5b7qb7kro03fvka3o8kbq262wwm&callback=?';
var url = 'https://api.twitch.tv/kraken/';
var result = {};
streamers.forEach(function(stream) {
$.getJSON(url + 'streams/' + stream + cb).success(function(data) {
var streaming = (data.stream === null) ? false : true;
result.push(stream + " - " + streaming);
console.log(result);
});
});
});
Moving from looking at the results to using the results, you'll want to break things up a bit:
$(document).ready(function() {
var streamers = ["freecodecamp", "GeoffStorbeck", "terakilobyte"];
var cb = '?client_id=5j0r5b7qb7kro03fvka3o8kbq262wwm&callback=?';
var url = 'https://api.twitch.tv/kraken/';
var result = {};
var getStreams = streams.map(function (stream) {
return $.getJSON(url + 'streams/' + stream + cb).success(function(data) {
var streaming = (data.stream === null) ? false : true;
result.push(stream + " - " + streaming);
console.log(result);
});
});
var onResultsSuccess = function (results) {
console.log("I have all my streams, let's take a look at them:", results);
};
var onResultsFail = function () {
console.log("Something's up!", arguments);
};
$.when(getStreams).then(onResultsSuccess, onResultsFail);
});
Untested so assume it's pseudo!
Not only should you defer the alert to after the calls, you should not loop Ajax externally..
Here is what I would suggest
function getStream() {
if (result.length >= streamers.length) return;
var stream = streamers[result.length];
$.getJSON(url + 'streams/' + stream + cb).success(function(data){
var streaming = (data.stream === null) ? false : true;
result.push(stream + " - " + streaming);
getStream();
});
}
var streamers = ["freecodecamp", "GeoffStorbeck", "terakilobyte"];
var cb = '?client_id=5j0r5b7qb7kro03fvka3o8kbq262wwm&callback=?';
var url = 'https://api.twitch.tv/kraken/';
// This should be initialised as an array, not an object!
var result = [];
$(function() {
getStream();
});
Related
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 2 years ago.
Trying to find a value in an excel file using the XLSX library:
The function works, it finds the value, however the output is undefined, even though the debugging say the value is found.
Here's the function:
var getValsFromExcel = function(sheet,idcol, valcol, val){
var workbook = new Excel.Workbook();
workbook.xlsx.readFile(__dirname + '/assets/gu.xlsx')
.then(function() {
var worksheet = workbook.getWorksheet(sheet);
worksheet.eachRow({ includeEmpty: false }, function(row, rowNumber) {
console.log("Row " + rowNumber + " = " + JSON.stringify(row.values));
console.log(row.values[idcol]);
console.log('checking ' + row.values[idcol] + ' = ' + val + ' ' + (row.values[idcol] == val))
if (row.values[idcol] == val){
console.log('Value found! its ' + row.values[valcol])
//getValsFromExcel = row.values[valcol];
return row.values[valcol];
}
});
});
}
var ans = getValsFromExcel('yesno',3, 4, tobj["respondent_consent"]);
console.log('Q1 answer = ' + ans);
Here's the console output:
Q1 answer = undefined
Row 1 = [null,"UID","Delete(Y/N)","field: yesno_key_value","field: yesno_display_text"]
field: yesno_key_value
checking field: yesno_key_value = yes false
Row 2 = [null,"5b45fe42f7fe481d8442d5e94b894b45","N","yes","Si"]
yes
checking yes = yes true
Value found! its Si
Row 3 = [null,"b65ba5a1a3814a87b4571e8d477307aa","N","no","No"]
no
checking no = yes false
getValsFromExcel asynchronous, here is the correction:
var getValsFromExcel = function(sheet,idcol, valcol, val){
var workbook = new Excel.Workbook();
return workbook.xlsx.readFile(__dirname + '/assets/gu.xlsx')
.then(function() {
var worksheet = workbook.getWorksheet(sheet);
let answer = null;
worksheet.eachRow({ includeEmpty: false }, function(row, rowNumber) {
console.log("Row " + rowNumber + " = " + JSON.stringify(row.values));
console.log(row.values[idcol]);
console.log('checking ' + row.values[idcol] + ' = ' + val + ' ' + (row.values[idcol] == val))
if (row.values[idcol] == val){
console.log('Value found! its ' + row.values[valcol])
//getValsFromExcel = row.values[valcol];
answ = row.values[valcol];
return;
}
});
return answer;
});
}
getValsFromExcel('yesno',3, 4, tobj["respondent_consent"])
.then( answer => console.log('Q1 answer = ' + ans) );
There are a couple things going on here. First, you're not returning any value from getValsFromExcel, so doing var ans = getValsFromExcel() will always be undefined (the default return value of any function).
But even if you do return workbook.xlsx.readFile(__dirname + '/assets/gu.xlsx').then() // ... you won't get the value you have in console.log('Value found! its ' + row.values[valcol]), because workbook.xlsx.readFile returns a Promise.
If you're on a recent version of Node, you can add that return before your readFile call, and then do this
async function main() {
var ans = await getValsFromExcel()
console.log(ans)
}
main()
Edit: sorry for the half answer initially. I don't like the SO editor and apparently I smashed a key combo that prematurely saved it.
Here's another solution showing this without async/await. Remember: async/await is just Promises with special syntax, but it works the same.
getValsFromExcel(/* args */)
.then((value) => {
console.log(value)
})
This is the same thing. We either have to await the Promise, or we have to chain a then with a callback that will be invoked with the value you return in your Promise chain in the getValsFromExcel function.
There were a number of bugs in the original code... here's a further breakdown for completeness' sake:
const Excel = require("exceljs")
var getValsFromExcel = function (sheet, idcol, valcol, val) {
var workbook = new Excel.Workbook()
return workbook.xlsx
.readFile(__dirname + "/assets/gu.xlsx")
.then(function () {
var worksheet = workbook.getWorksheet(sheet)
// let's initialize with some value.
// undefined would work as well, but we can be explicit if it's not found
// and make it null.
let result = null
worksheet.eachRow({ includeEmpty: false }, function (row, rowNumber) {
if (row.values[idcol] == val) {
// ok now we reassign result to the value we want
result = row.values[valcol]
}
})
// In order to have access to the value in the next `then`
// of your Promise chain, you _must_ return it.
return result
})
.then((value) => {
console.log("Value = " + value)
})
}
// alternate version using async/await
var getValsFromExcel = async function (sheet, idcol, valcol, val) {
var workbook = new Excel.Workbook()
// wait for the asynchronous code to resolve
await workbook.xlsx.readFile(__dirname + "/assets/gu.xlsx")
// after this point, workbook has been mutated and now contains the file's data
var worksheet = workbook.getWorksheet(sheet)
let result = null
worksheet.eachRow({ includeEmpty: false }, function (row, rowNumber) {
if (row.values[idcol] == val) {
result = row.values[valcol]
}
})
console.log("Value = " + result)
}
getValsFromExcel("Sheet1", 2, 2, "Dulce")
I can not let emiter emits the signal which is returned from pg module before got called back. Here is my situation.
questionHandler_upqid.js :
var express = require('express');
var router = express.Router();
var pg = require('pg');
var connectionString = process.env.DATABASE_URL || 'postgres://admin:admin#localhost:5432/mydb';
var question_id;
var resource_id = [];
//var err_ = []; //Move to local function
var mcres = [];
exports.addResourceFile = function(req, res, err, client, done, callback){
var cb_is_func = (typeof(callback)==='function');
console.log('addResourceFile function called');
var err_ = [];
var resourcefiles = req.body.resourcefiles;
var idx = 0;
//Use local function because of err + client + done scope.
exports.addResourceFile_inside = function (element, index, array){
console.log('addResourceFile_inside function called, index : ' + index);
var func_ = 'SELECT Dugong.resourcefile_Add($1,$2,$3)';
//rfdata = resource file data.
var rfdata = [element.fileurl,
element.name,
element.type];
console.log(rfdata);
var addResourceFile_ = client.query(func_, rfdata);
//Add underscore (_) to prevent confusion.
addResourceFile_.on('error', function(err){
console.log('error signal from addResourceFile');
var tempdata = {success : false,
id : -1,
reason : {errmsg : err.detail,
errid : 'addResourceFile' + index }}; //plus index to spot record
err_.push(tempdata);
});
addResourceFile_.on('row', function(row){
console.log('row signal from addResourceFile_');
resource_id.push(row.resourcefile_add);
});
console.log('hello world');
callback(err_, resource_id);
return ;
};
/*
//Want sequential run of these.
//But it does not.
resourcefiles.forEach(exports.addResourceFile_inside);
callback(err_, resource_id);
return ;
*/
//Emitter emits lastest. callback executed first.
exports.action = function(){
resourcefiles.forEach(exports.addResourceFile_inside);
};
exports.summary = function(){
callback(err_, resource_id);
};
exports.summary(
exports.action()
);
};
My unit test : questionHandler_upqid.js
var httpMocks = require('node-mocks-http');
var test = require('unit.js');
var real_questionHandler = require('../routes/questionHandler_upqid.js');
var proxyquire = require('proxyquire'),
pgStub = { };
var events = require('events');
describe('questionHandler_upqid : ', function () {
var request;
beforeEach(function (){
request = httpMocks.createRequest({
method: 'PUT',
url: '/questions',
params: {id : 1 },
body : { questiontype : 'Multiple Choice',
problemtext : 'Find x when x is 4 + 2',
excerpttext : 'Somewhere I belong',
solutiontext : '6',
authorid: 'Sarit1',
subjectid: '2',
lastmod: '2099-0101 00:00:02',
resourcefiles : [{"name":"penguin","type":"jpeg","fileurl":"/1.jpeg"},
{"name":"penguin","type":"jpeg","fileurl":"/2.jpeg"},
{"name":"penguin","type":"jpeg","fileurl":"/3.jpeg"},
{"name":"penguin","type":"jpeg","fileurl":"/4.jpeg"},
{"name":"penguin","type":"jpeg","fileurl":"/5.jpeg"}],
mcresponses : [{"text":"BX","image":"A1","iscorrect":false},
{"text":"BW","image":"A2","iscorrect":false},
{"text":"ACRA","image":"A3","iscorrect":true},
{"text":"GxM","image":"A4","iscorrect":false}]
//Do not change lenght of mcresponse.
//mcres_length is used in 'should add MCResponse with error'
}
});
pgStub.end = function(){};
});
it('should add ressource file with error', function(done){
//emitter emit 'error'
var client = {};
client.query = function(querystr, data){
var emitter = new events.EventEmitter();
setTimeout(function(){
console.log('emit diode add resource file with error');
error = {detail : 'Resrc file can be changed!'};
emitter.emit('error', error);
},0);
console.log('add emitters');
return emitter;
};
var response = httpMocks.createResponse();
var questionHandler_upqid = proxyquire('../routes/questionHandler_upqid.js', { 'pg' : pgStub} );
questionHandler_upqid.addResourceFile(request, response, null, client, null, function(err, resrc_id){
console.log(err);
console.log(resrc_id);
done();
});
});
});
This code still emit after callback has been called.
var p1 = new Promise(function(){
resourcefiles.forEach(exports.addResourceFile_inside);
});
p1.then(callback(err_, resource_id));
I got 2 blank arrays of err_ and resource_id. Then come up with signal from emitter. Actually I want emitter to be executed first then callback. The callback should return me 2 arrays. They are err_ and resource_id.
You'll want to tie the callback to one of the pg's events to know that the query has completed before invoking it.
Along with 'row' and 'error' events, its Query objects will also emit an 'end' event when all rows have been received successfully:
addResourceFile_.on('row', function(row){
console.log('row signal from addResourceFile_');
resource_id.push(row.resourcefile_add);
});
addResourceFile_.on('end', function () {
callback(null, resource_id);
});
Though, the 'end' event won't be emitted if there are errors (so you can safely assume null), so you'll want to invoke the callback from the 'error' event as well to cover both success and failure:
addResourceFile_.on('error', function (err) {
console.log('error signal from addResourceFile');
var tempdata = {
success : false,
id : -1,
reason : {
errmsg : err.detail,
errid : 'addResourceFile' + index // plus index to spot record
}
};
callback(tempdata, null);
});
I don't know this is a good coding style or not, but just for now. I go by this.
addResourceFile_.on('error', function(err){
console.log('error signal from addResourceFile');
var tempdata = {success : false,
id : -1,
reason : {errmsg : err.detail,
errid : 'addResourceFile' + index }}; //plus index to spot record
err_.push(tempdata);
console.log(idx);
if(idx === array.length - 1){
callback(err_, resource_id);
}
idx = idx + 1;
});
addResourceFile_.on('row', function(row){
console.log('row signal from addResourceFile_');
resource_id.push(row.resourcefile_add);
if(idx === array.length - 1){
callback(err_, resource_id);
}
idx = idx + 1;
});
Basically I have this code. I want to save the result of the request in the callback to a variable I can then return to another part of my program.
var request = require('request');
var geocodeLoc = function(location)
{
var result;
var baseURL = 'https://maps.googleapis.com/maps/api/geocode/json?address=' + location;
request(baseURL, function(e, r, b){
if(!e && r.statusCode == 200){
result = (JSON.parse(b).results[0].geometry.location);
console.log(result);
}
});
return result
}
console.log( geocodeLoc('Hoxton') );
the result of console.log(geocodeLoc('Hoxton')) is returning before console.log(result) is executed.
The really frustrating thing is that the result of console.log(result) is exactly what I want.
Here is the output of the program
undefined
{ lat: 51.535638, lng: -0.08934399999999999 }
All I want is for geocodeLoc to return result. I'm kind of unfamiliar with NodeJS any help is much appreciated
This happens because your function makes an async request which has a delay, in order not to block the whole thread, node is continuing the execution, that's why your result logs after the first console log.You can use promises. I recommend promise-simple or q.
The result will look like this:
var request = require('request');
var Promise = require('promise-simple');
var geocodeLoc = function(location)
{
var result;
var d = Promise.defer();
var baseURL = 'https://maps.googleapis.com/maps/api/geocode/json?address=' + location;
request(baseURL, function(e, r, b){
if(!e && r.statusCode == 200){
result = (JSON.parse(b).results[0].geometry.location);
console.log(result);
d.resolve(result);
}else{
d.reject(e);
}
});
return d;
}
// use it like this:
geocodeLoc('Hoxton').then(function(result){
console.log('here is your result',result);
});
or if you really do not want to use another module, you can achive the same result using a callback function:
var request = require('request');
var geocodeLoc = function(location, callback)
{
var result;
var baseURL = 'https://maps.googleapis.com/maps/api/geocode/json?address=' + location;
request(baseURL, function(e, r, b){
if(!e && r.statusCode == 200){
result = (JSON.parse(b).results[0].geometry.location);
console.log(result);
callback(null, result);
}else{
callback(e);
}
});
}
// use it like this:
geocodeLoc('Hoxton', function(err, result){
console.log('here is your result',result);
});
For callback functions is recommended that you always return the error as first parameter.
I'm trying to repurpose a "legacy function" to pass a function with parameters into another function and get called. I've seen bits and pieces of what I'm looking for, but my arguments keep getting passed as a single string. This is the calling code - the 4th parameter (starts with '_delRideNew') is what I need to call.
MODAL.show("Confirm", "Are you sure you want to delete this ride?","Yes","_delRideNew('" + id + "','" + day + "','" + attuid + "')","No","MODAL.hide();")
Here is the MODAL.show code (using easyui):
MODAL.show = function(title, msg, okbtn, okcallback, canbtn, cancallback) {
if(arguments.length > 2) {
$.messager.defaults.ok = okbtn;
$.messager.defaults.cancel = canbtn;
}
else {
$.messager.defaults.ok = "OK";
$.messager.defaults.cancel = "Cancel";
}
if(arguments.length === 6) {
var me = $.messager.confirm(title, msg, function(r) {
if(r) {
//parse out function and args
var pos = okcallback.indexOf("(");
var func = okcallback.substring(0,pos);
var argss = okcallback.substring(pos,okcallback.length);
argss = argss.replace("(", "");
argss = argss.replace(")", "");
var argArray = argss.split(",");
window[func](argArray);
}
else {
cancallback;
}
});
me.window('move',{
left:400,
top:document.body.scrollTop+document.documentElement.scrollTop+200
});
}
else {
confirm(msg, function(r) {
if(r) {
return true;
}
else {
return false;
}
});
}
}
The problem is when the window[func] gets called it passes the array as a single string here:
function _delRideNew(id,day,attuid){
alert(id); //shows all 3 params as a string
var txtURL = 'delRide.cfm?tech_attuid=' + attuid + '&ride_date=#getParam("month")#/' + day + "/#getParam("year")#";
SYS.loadScript(txtURL);
status = txtURL;
}
It's very much possible that I'm doing this completely wrong, but any help would be, well..helpful.
I'm working on a project where inside a loop I need to make multiple JSON calls. As soon as I exit that loop I need to work with the results of all the calls I made. I'm having a hard time understanding how to make these calls in such a way that my order of operation works out. My code to work with the results always executes before the calls to the service have completed. I created a jsfiddle to demonstrate and am including the code here.
http://jsfiddle.net/VEkrf/3/
var sourceData = { "fooIndex": "foo",
"barIndex": "bar"
}
var destinationData = {};
for (var sourceIndex in sourceData) {
$.getJSON('http://echo.jsontest.com/' + sourceIndex + '/' + sourceData[sourceIndex] + '?callback=?', null, function (result) {
for (var resultIndex in result) {
alert("Adding " + resultIndex + " : " + result[resultIndex]);
destinationData[resultIndex] = result[resultIndex];
}
});
}
if (Object.keys(destinationData).length == 0) {
alert("Destination not yet populated");
}
else {
alert("Eureka! You did it!");
}
This looks like a job for jQuery Deferred Object, and my sidekick $.when!
Pass all the $.getJSON calls to $.when, and when they are all done, I'll will call a function with all the results.
Check this out:
var sourceData = {
"fooIndex": "foo",
"barIndex": "bar"
};
var destinationData = {};
// Array of AJAX calls
var AJAX = [];
for (var sourceIndex in sourceData) {
AJAX.push($.getJSON('http://echo.jsontest.com/' + sourceIndex + '/' + sourceData[sourceIndex] + '?callback=?'));
}
// Apply is needed to pass each element as a parameter
$.when.apply($, AJAX).done(function(){
// This function will be called when all the AJAX calls are done
// The arguments of the functin are the responses from each request
for(var i = 0, len = AJAX.length; i < len; i++){
var result = arguments[i][0];
//arguments: [resultObj, 'success', jqXHR]
for (var resultIndex in result) {
alert("Adding " + resultIndex + " : " + result[resultIndex]);
destinationData[resultIndex] = result[resultIndex];
}
}
alert("Eureka! You did it!");
});
NOTE: Since this is asynchronous, destinationData won't be available until the callback is triggered. Put any code that uses that inside the .done() callback.
Since you are using jQuery already I suggest exploring the queue functions. You can queue the ajax calls and then in the success handlers call the de-queue or next function. this way they go in succession. The last item you add to the queue is your function that handles the returned data.
var sourceData = {
"fooIndex": "foo",
"barIndex": "bar"
};
var destinationData = {};
$(function () {
console.debug('ready');
for (var sourceIndex in sourceData) {
console.debug('for Loop');
$(document).queue('ajax', function (next) {
$.getJSON('http://echo.jsontest.com/' + sourceIndex + '/' + sourceData[sourceIndex] + '?callback=?', null, function (result) {
for (var resultIndex in result) {
alert("Adding " + resultIndex + " : " + result[resultIndex]);
destinationData[resultIndex] = result[resultIndex];
next();
}
});
});
}
$(document).queue('ajax', function (next) {
alert("Eureka! You did it!");
});
$(document).dequeue('ajax');
});
I do this all the time for 'synchronus' ajax.
here is an example of what i am talking about