Can someone explain why callback function after loadData(symbol) is not being called? I have this function:
function plotChart(symbol) {
alert("symbol: " + symbol);
var data = loadData(symbol, function(){
console.log("data: ",data);
});
}
I'm expecting log.console to execute after loadData completes, but nothing gets output.
In case it matters, here is the loadData function.
function loadData(symbol) {
count = 0;
quotes = [];
$.getJSON("http://localhost:8080/springboot-crud-rest/api/v1/quotes-between?symbol=IBM&startDate=2020-01-01&endDate=2020-09-30", function(data) {
data.forEach(function(item){
var quote = {};
quote.date = item.id.date.substring(0,10);
quote.open = item.open;
quote.high = item.high;
quote.low = item.low;
quote.close = item.close;
quote.volume = item.volume;
quotes.push(quote); //put quote in array
});
console.log("quotes: ",quotes);
return quotes;
});
}
Here the console.log successfully prints out the quotes correctly.
EDIT:
Per #Aluan, I changed the code to look like this:
function plotChart(symbol) {
loadData(symbol).then(function(data) {
console.log("data: ", data);
});
}
function loadData(symbol) {
quotes = [];
$.getJSON("http://localhost:8080/springboot-crud-rest/api/v1/quotes-between?symbol=" +symbol +"&startDate=2020-01-01&endDate=2020-09-30")
.then(function (data) {
const quotes = data.map(function(item) {
return {
date: item.id.date.substring(0,10),
open: item.open,
high: item.high,
low : item.low,
close: item.close,
volume: item.volume
};
});
console.log("quotes: ",quotes);
return quotes;
});
}
But now I'm getting error:
Uncaught TypeError: Cannot read property 'then' of undefined
at plotChart (moneymachine.html:120)
at HTMLTableRowElement.
Line 120 is loadData(symbol).then(function(data) {
The definition of loadData is essential to understanding the reason.
Given
function loadData(symbol) {
count = 0;
quotes = [];
$.getJSON("http://localhost:8080/springboot-crud-rest/api/v1/quotes-between?symbol=IBM&startDate=2020-01-01&endDate=2020-09-30", function (data) {
data.forEach(function (item) {
var quote = {};
quote.date = item.id.date.substring(0, 10);
quote.open = item.open;
quote.high = item.high;
quote.low = item.low;
quote.close = item.close;
quote.volume = item.volume;
quotes.push(quote); //put quote in array
});
console.log("quotes: ", quotes);
return quotes;
});
}
We can see that it defines one parameter, symbol. You may pass additional arguments when calling it, but it clearly does not use them.
When you write
var data = loadData(symbol, function () {
console.log("data: ", data);
});
The second argument is simply ignored.
Here's an example of loadData that would accept and make use of a second argument that's a callback.
function loadData(symbol, callback) {
count = 0;
quotes = [];
$.getJSON("http://localhost:8080/springboot-crud-rest/api/v1/quotes-between?symbol=IBM&startDate=2020-01-01&endDate=2020-09-30", function(data) {
data.forEach(function(item){
var quote = {};
quote.date = item.id.date.substring(0, 10);
quote.open = item.open;
quote.high = item.high;
quote.low = item.low;
quote.close = item.close;
quote.volume = item.volume;
quotes.push(quote); //put quote in array
});
callback(data) // here
console.log("quotes: ", quotes);
});
}
However, this definition of loadData is suboptimal at best. It should make use of the thenable return by jQuery's $.getJson, to make the code clearer.
function loadData(symbol) {
return $.getJSON("http://localhost:8080/springboot-crud-rest/api/v1/quotes-between?symbol=IBM&startDate=2020-01-01&endDate=2020-09-30")
.then(function (data) {
const quotes = data.map(function (item) {
return {
date: item.id.date.substring(0, 10),
...item
};
});
console.log("quotes: ", quotes);
return quotes;
});
}
Consumed as
loadData('something').then(function (data) {
console.log("data: ", data);
});
Related
How can I wait for the Braintree Transaction.search() function to return all data.
Right now it does not wait and just comes back with undefined return value.
Here is the code
I tried to use Meteor.asynwrap but that also does not work.
`
function getTrxns(cid) {
var future = new Future();
var trxns = [];
var i = 0
var stream = gateway.transaction.search(function (search) {
r = search.customerId().is(cid)});
stream.on("data", function(data){
i = i+1
trxns.push({
'id':data.id,
'amount':data.amount,
'crtDt': data.createdAt,
'ccType': data.creditCard.cardType,
'currency': data.currencyIsoCode,
'last4': data.creditCard.last4,
'expdt': data.creditCard.expirationDate
});
});
stream.on("end", function(){
// print the output in console
console.log('End Stream cnt: '+i);
return trxns;
});
stream.resume();
}
Meteor.methods({
findCustTrxns: function() {
var btId = Meteor.user().custBtId;
if (!btId) { return []; };
console.log('findCustTrxns cusBtId: '+btId);
var xx = getTrxns(btId);
console.log('xx len :'+xx.length);
}
});
OUTPUT is:
I20170509-15:22:09.095(0)? findCustTrxns cusBtId: 232057823
I20170509-15:22:09.095(0)? Exception while invoking method 'findCustTrxns' TypeError: Cannot read property 'length' of undefined
I20170509-15:22:09.095(0)? End Stream cnt: 56
Found a way to make it work:
1. Added a callback function
function getTrxns(cid,callback )
2. invoked the callback in stream.on('end;..) Here is the code
function getTrxns(cid,callback ) {
var trxns = [];
var i = 0
var stream = gateway.transaction.search(function (search) {
r = search.customerId().is(cid)});
stream.on("data", function(data){
i = i+1
trxns.push({
'id':data.id,
});
});
stream.on("end", function(){
// print the output in console
console.log('End Stream cnt: '+i);
callback('', trxns);
});
stream.resume();
}
3. Changed the Meteor Method :
findCustTrxns: function(btId) {
if (!btId) { return []; };
console.log('findCustTrxns cusBtId: '+btId);
var trxns = [];
var i = 0;
var fn = Meteor.wrapAsync(getTrxns); //made it a synchronous call
try {
var res = fn(btId);
if (res) {
console.log('Got data from getTrxns ');
return res;
}
} catch( err) {
console.log('Error calling hetTrxns '+err);
}
}, //findCustTrxns
Now I am able to get the Transactions. Hope it helps
Here is the code I am writing tests for:
'use strict';
var internals = {};
var _ = require('lodash');
module.exports = {
initialize: function (query) {
internals.query = query;
},
createField: function (fieldId, accountId, payload) {
function callQuery (parList) {
var query = 'INSERT into fields VALUES (:uuid, :accountId, :shortcutName, :displayName, :fieldType, :widgetType, :columnOrder, :options, :required, NULL)';
return internals.query(query, parList, function () { return fieldId; });
}
var increment = 10;
var parameterList = {
'uuid': fieldId,
'accountId': accountId,
'shortcutName': payload.shortcutName,
'displayName': payload.displayName,
'fieldType': payload.fieldType,
'widgetType': payload.widgetType,
'columnOrder': payload.columnOrder,
'options': JSON.stringify(payload.options) || null,
'required': payload.required || 'f'
};
if (!payload.columnOrder) {
var columnQuery = 'SELECT MAX(column_order) from fields';
return internals.query(columnQuery, {}, function (x) {return x; })
.then(function (results) {
var highestColumnOrder = results[0]['MAX(column_order)'];
var newHighestColumnOrder = Math.ceil(highestColumnOrder / 10) * 10;
if (newHighestColumnOrder > highestColumnOrder) {
parameterList.columnOrder = newHighestColumnOrder;
} else {
parameterList.columnOrder = newHighestColumnOrder + increment;
}
return callQuery(parameterList);
});
} else {
return callQuery(parameterList);
}
},
getFieldsByAccountId: function(accountId, showDeleted) {
var callQuery = function(paramList) {
var query = 'SELECT ' + paramList.columns.join(", ") + ' FROM fields WHERE account_id = :account_id';
if (!showDeleted) {
query += ' AND archived_at IS NULL';
}
return internals.query(query, paramList, function(rows) {
return _.each(rows, function(row) {
if(row.options) {
row.options = JSON.parse(row.options);
}
row.required = !!row.required;
});
});
};
var columnList = ["uuid", "account_id", "shortcut_name", "display_name", "field_type", "required", "column_order", "options"];
var paramList = {'account_id': accountId};
if (showDeleted) {
columnList.push("archived_at");
}
_.extend(paramList, {'columns': columnList});
return callQuery(paramList);
}
};
Here is my test:
'use strict';
var assert = require('assert');
var sinon = require('sinon');
var Promise = require('bluebird');
var proxyquire = require('proxyquire');
var returnedValues = require('../../../return_values.js');
var fieldGateway = proxyquire('../../../../src/fields/lib/gateway', {});
describe('gateway', function () {
var accountId = 100;
var fieldId = 200;
var _query, sql, mockData, rows;
describe('createField', function() {
describe('is successful with a column order value', function () {
beforeEach(function() {
sql = 'INSERT into fields VALUES (:uuid, :accountId, :shortcutName, :displayName, :fieldType, :widgetType, :columnOrder, :options, :required, NULL)';
mockData = returnedValues.getFieldInputValues();
});
it("should only insert new field", function () {
_query = sinon.spy(function() { return Promise.resolve(); });
fieldGateway.initialize(_query);
fieldGateway.createField(fieldId, accountId, mockData);
mockData.accountId = accountId;
mockData.uuid = fieldId;
mockData.options = JSON.stringify(mockData.options);
assert.equal(sql, _query.getCall(0).args[0]);
assert.deepEqual(mockData, _query.getCall(0).args[1]);
});
it.only("_query should be called with the right sql statement and parameterList", function () {
_query = sinon.stub().returns(Promise.resolve(fieldId));
// _query.onCall(0).returns(Promise.resolve([{'MAX(column_order)': 10}]));
// _query.onCall(1).returns(Promise.resolve(fieldId));
fieldGateway.initialize(_query);
delete mockData.columnOrder;
fieldGateway.createField(fieldId, accountId, mockData);
console.log(_query.args);
assert.equal(sql, _query.getCall(0).args[0]);
fieldGateway.createField.restore();
});
});
});
});
The problem is that when the test runs, the only SQL query that runs is the SELECT statement. What should happen is one SQL statement runs, then an INSERT statement runs
This happens because bluebird is a true Promise/A+ compliant library. And by definition all chained promises must be run in a different execution tick. So only the first promise is executed synchronously (in same tick).
You should tell mocha to "wait" for the rest to act. You do this by specifying a done callback in your unit test and calling it accordingly when your promises finished their job
it.only("_query should be called with the right sql statement and parameterList", function (done) {
_query = sinon.stub().returns(Promise.resolve(fieldId));
fieldGateway.initialize(_query);
delete mockData.columnOrder;
fieldGateway.createField(fieldId, accountId, mockData)
.then(function(){
/// assertion code should be adjusted here
console.log(_query.args);
assert.equal(sql, _query.getCall(0).args[0]);
fieldGateway.createField.restore();
//tell Mocha we're done, it can stop waiting
done();
})
.catch(function(error) {
//in case promise chain was rejected unexpectedly
//gracefully fail the test
done(error);
};
});
Whenever you test your promise-returning functions you should always handle result in a .then
Here are the cloud functions namely 'batchReq1' and batchPromises.
In any case, if I know the exact number of promises pushed (Consider the size of results to be '2' in function batchPromises(results)) and executed through when(), I can handle the success response by passing that number of result parameters (In the below example request1, request2) in successCallBack of .then().
If I have to process the number of promises pushed to .when() dynamically, then, how can we handle this in SuccessCallBack? Unlike earlier scenario, we can't expect fixed number of results in the then method (batchPromises(results).then(function (result1, result2) {....)
batchReq1
Parse.Cloud.define("batchReq1", function (request, response) {
var results = request.params.imageArray;
batchPromises(results).then(function (result1, result2) {
console.log("Final Result:: Inside Success");
console.log("Final Result:: Inside Success result 1::::"+result1);
console.log("Final Result:: Inside Success result 2::::"+result2);
response.success();
}
// batchPromises(results).then(function (arraySuccess) {
//
// console.log("Final Result:: Inside Success");
// console.log("Final Result:: Inside Success:: Length:: "+arraySuccess.length);
// //Fetch all responses from promises and display
// var _ = require('underscore.js');
// _.each(arraySuccess, function (result) {
//
// console.log("Final Result:: " + result)
//
// });
//
//
// response.success();
//
// }
, function (error) {
console.log("Final Result:: Inside Error");
response.error(error);
});
});
batchPromises
function batchPromises(results) {
var promise = Parse.Promise.as();
var promises = [];
var increment = 0;
var isFromParallelExecution = false;
var _ = require('underscore.js');
_.each(results, function (result) {
var tempPromise = Parse.Promise.as("Promise Resolved ");
promises.push(tempPromise);
}
promise = promise.then(function () {
return Parse.Promise.when(promises);
});
}
increment++;
});
return promise;
}
this is how i handle this...
Parse.Cloud.define("xxx", function(request, response)
{
var channels = ["channel1", "channel2", "channel3", "channel4", "channel5"];
var queries = new Array();
for (var i = 0; i < channels.length; i++)
{
var query = new Parse.Query(channels[i]);
queries.push(query.find());
}
Parse.Promise.when(queries).then(function()
{
var msg = "";
for (var j = 0; j < arguments.length; j++)
{
for (var k = 0; k < arguments[j].length; k++)
{
var object = arguments[j][k];
msg = msg + " " + object.get('somefield');
}
}
response.success(msg);
});
});
Either you just use the arguments object to loop over the results, or you build your arraySuccess without when - it doesn't make much sense here anyway as you batch the requests (executing them sequentially), instead of executing them in parallel:
function batchPromises(tasks) {
var _ = require('underscore.js');
_.reduce(tasks, function (promise, task) {
return promise.then(function(resultArr) {
var tempPromise = Parse.Promise.as("Promise Resolved for "+taks);
return tempPromise.then(function(taskResult) {
resultArr.push(taskResult);
return resultArr;
});
});
}, Parse.Promise.as([]));
}
If you actually wanted to execute them in parallel, use a simple
function batchPromises(tasks) {
var _ = require('underscore.js');
return Parse.Promise.when(_.map(tasks, function (task) {
return Parse.Promise.as("Promise Resolved for "+taks);
}).then(function() {
return [].slice.call(arguments);
});
}
I am trying to return the value of itemInfo[0] from within this nested function. Can anyone help how I should return this value with a callback ?
function findItem(item) {
var itemInfo = [];
Item.findItem(item, function(err, itemInfo){
itemInfo[0].info = _.unescape(itemInfo[0].info);
itemInfo[0].title = _.unescape(itemInfo[0].title);
// console.log(itemInfo[0]);
return itemInfo[0];
});
};
Set the cb argument to null after you use it and check for its validity before calling.
function findItem(item, cb) {
var itemInfo = [];
Item.findItem(item, function(err, itemInfo){
if (cb) {
itemInfo[0].info = _.unescape(itemInfo[0].info);
itemInfo[0].title = _.unescape(itemInfo[0].title);
// console.log(itemInfo[0]);
cb( itemInfo[0] );
cb = null;
}
});
};
What if you returned the returned value?
function findItem(item) {
var itemInfo = [];
return Item.findItem(item, function(err, itemInfo){
itemInfo[0].info = _.unescape(itemInfo[0].info);
itemInfo[0].title = _.unescape(itemInfo[0].title);
// console.log(itemInfo[0]);
return itemInfo[0];
});
};
I'm new to NodeJS and I was trying to figure out stuff but unfortunately I couldn't find information on it with my knowledge.
Basicly, I want to use a variable from a function inside another function wich is a child of the main function.
Here is my code :
http.get(url, function(res) {
var body = '';
res.on('data', function(chunk) {
body += chunk;
});
res.on('end', function() {
var jsonResult = JSON.parse(body);
for (var i=0;i<5;i++)
{
gameId = jsonResult.gameList[i].gameId;
url = 'http://somesite.com/' + gameId + '/0/token';
http.get(url, function(res) {
var body = '';
res.on('data', function(chunk) {
body += chunk;
});
res.on('end', function() {
jsonRes = JSON.parse(body);
switch(i)
{
case 0:
var elo0 = jsonRes.interestScore;
module.exports.elo0 = elo0;
break;
case 1:
var elo1 = jsonRes.interestScore;
module.exports.elo1 = elo1;
break;
case 2:
var elo2 = jsonRes.interestScore;
module.exports.elo2 = elo2;
break;
case 3:
var elo3 = jsonRes.interestScore;
module.exports.elo3 = elo3;
break;
case 4:
var elo4 = jsonRes.interestScore;
module.exports.elo4 = elo4;
break;
}
});
}).on('error', function(e) {
console.log("Got error: ", e);
});
}
});
}).on('error', function(e) {
console.log("Got error: ", e);
});
Note that I didn't include everything, just the problematic part.
I want to use the variable i from the loop inside the switch but it doesn't work.
The problem here is that you are referencing i in your switch statement within an asynchronous callback. When you do this, you won't get the value of i at the time the function is created, but the final value of i at the end of the loop iteration.
There are a couple of ways to fix this - both involve trapping the current loop value of i in a closure for later reference by the callback.
for example:
for (var i=0;i<5;i++)
{
(function(idx) {
gameId = jsonResult.gameList[idx].gameId;
url = 'http://somesite.com/' + gameId + '/0/token';
http.get(url, function(res) {
...
res.on('end', function() {
jsonRes = JSON.parse(body);
switch(idx)
{
case 0:
break;
...
}
});
...
});
})(i);
}
Here, an anonymous function is created for each pass through the loop and called immediately passing the current value of the loop counter i as the incoming parameter idx.
Another approach (as mentioned in the comments above) is to refactor the inner part of your loop into a separate function and call it passing all necessary context:
function scoreHandler(jsonResult, idx) {
var gameId = jsonResult.gameList[idx].gameId;
var url = 'http://somesite.com/' + gameId + '/0/token';
http.get(url, function(res) {
...
res.on('end', function() {
jsonRes = JSON.parse(body);
switch (idx) {
...
}
});
})
.on('error', function(e) {
console.log("Got error: ", e);
});
}
Your refactored loop would look something like:
res.on('end', function() {
var jsonResult = JSON.parse(body);
for (var i = 0; i < 5; i++) {
scoreHandler(jsonResult, i);
}
});