I'm attempting to recreate a Python script I wrote in Node/js and I'm having trouble wrapping my head around the asynchronous/callback way of it all.
The script is fairly simple and uses two basic HTTP requests to eBay's API. The first request gets a list of resulting item ids, then the second request gets specific information on each item (description/listing information etc). In Python this was fairly simple and I used a simple loop. It wasn't fast by any means, but it worked.
In javascript, however, I'm struggling to get the same functionality. My code right now is as follows:
var ebay = require('ebay-api');
var params ={};
params.keywords = ["pS4"];
var pages = 2;
var perPage = 2;
ebay.paginateGetRequest({
serviceName: 'FindingService',
opType: 'findItemsAdvanced',
appId: '',
params: params,
pages: pages,
perPage: perPage,
parser: ebay.parseItemsFromResponse
},
function allItemsCallback(error,items){
if(error) throw error;
console.log('FOUND', items.length, 'items from', pages, 'pages');
for (var i=0; i<items.length; i++){
getSingle(items[i].itemId);
}
}
);
function getSingle(id){
console.log("hello");
ebay.ebayApiGetRequest({
'serviceName': 'Shopping',
'opType': 'GetSingleItem',
'appId': '',
params: {
'ItemId': id ,
'includeSelector': 'Description'
}
},
function(error, data) {
if (error) throw error;
console.dir(data); //single item data I want
}
);
}
This is one attempt of many, but I'm recieving "possible EventEmitter memory leak detected" warnings and it eventually breaks with a "Error:Bad 'ack' code undefined errorMessage? null". I'm fairly sure this just has to do with proper utilization of callbacks but I'm unsure how to properly go about it. Any answers or help would be greatly appreciated. I apologize if this isn't a good question, if so please let me know how to correctly go about asking.
Node.js's asynchronous event chain is built on callbacks. Rather than:
getSingle(items[i].itemId);
You'll need to write a callback into that function that executes once the parent function is complete:
getSingle(items[i].itemId, function(err, data) {
// now you can access the data
});
And because ebay.ebayApiGetRequest is a lengthy function, the callback that tells its parent function that it's done must be called after that completes, like so:
ebay.ebayApiGetRequest({
//
},
function(error, data) {
callback(error, data);
}
);
But of course, if the parent function getSingle doesn't support a callback, then it won't go anywhere. So you'll need to support a callback param there as well. Here's the full script, rewritten using the event-driven callback model:
var ebay = require('ebay-api');
var async = require('async');
var params = {};
params.keywords = ["pS4"];
var pages = 2;
var perPage = 2;
ebay.paginateGetRequest({
serviceName: 'FindingService',
opType: 'findItemsAdvanced',
appId: '',
params: params,
pages: pages,
perPage: perPage,
parser: ebay.parseItemsFromResponse
},
function allItemsCallback(error, items) {
if (error) throw error;
console.log('FOUND', items.length, 'items from', pages, 'pages');
async.each(items, function(item, callback) {
getSingle(item.itemId, function(err, data) {
callback(err, data);
});
}, function(err, results) {
// now results is an array of all the data objects
});
}
);
function getSingle(id, callback) {
console.log("hello");
ebay.ebayApiGetRequest({
'serviceName': 'Shopping',
'opType': 'GetSingleItem',
'appId': '',
params: {
'ItemId': id,
'includeSelector': 'Description'
}
},
function(error, data) {
if (error) throw error;
console.dir(data); //single item data I want
callback(error, data);
}
);
}
Related
I have a MySQL table with a url column and I want to get a list of every url value in the table. Structure:
In PHP using Laravel I could have done something like this to get an array of every url column value:
$boards = Board::all()->lists('url');
However, I cannot figure out how to do this using Sails JS. I've noticed that find() without any argument gets every row in the database but I can't find anything in the documentation that would allow me to either get a list of the values for the url column or a way to iterate over the returned collection.
I've tried this:
var boards = Board.find().exec(function(error, _boards) {
if(error)
{
return response.negotiate(error);
}
return _boards;
});
However, I can't actually seem to iterate over the returned data.
Any thoughts?
EDIT:
Full HomepageController.js:
module.exports = {
index: function (request, response) {
var data = {
currentDate: (new Date()).toString(),
boards: Board.query('SELECT url FROM board', function (error, results) {
if (error) {
return response.negotiate(error);
}
return results;
})
};
return response.view('homepage', data);
}
};
EDIT: Working Code:
module.exports = {
index: function (request, response) {
Board.query('SELECT url FROM board', function(error, results) {
if(error)
{
return response.negotiate(error);
}
return response.view('homepage', { currentDate: (new Date()).toString(), boards: results });
});
}
};
You can use a raw .query() to select individual columns:
Board.query('SELECT url FROM board', function (err, results) { ... });
However, keep in mind that your data will still look like this:
[{url: 'row 1 value'}, {url: 'row 2 value'}, ...]
I'm using async.mapLimit to make some concurrency procedures upon an array with limit of 10:
async.mapLimit(files, 10, function(file, callback) {
... etc...
}, function(error, files) {
... etc..
});
Inside the main function, i'm executing a async operation with child_process, and if everything happen as it should, i just call the callback:
callback(null, files);
But... when something bad happens, i also NEED call the callback passing the file, because i don't want end everything, i just assign the file with a error property and call the callback:
file.error = error;
callback(null, file);
So, when the second async.mapLimit callback is fired, i have an array of files:
, function(error, files) {
console.log(files);
});
output:
[
{
name: 'file_2',
error: 'something'...
},
{
name: 'file_1',
...etc
}
]
So, i need separate the files that failed, doing:
var failedFiles = [];
var okFiles = [];
files.forEach(function(file) {
if (file.error)
failedFiles.push(file);
else
okFiles.push(file;
});
I would like to know if isn't possible to return the files that failed as an array, and access then by the parameter error of the second async.mapLimit callback.
Thanks in advance :).
async.mapLimit() will stop immediately when an iteration "returns" an error, so it's not possible to do what you want.
As an alternative, instead of using async.mapLimit() you could use async.eachLimit() and push the file objects into the respective array inside the iterator function:
var failedFiles = [];
var okFiles = [];
async.eachLimit(files, 10, function(file, callback) {
if (SOME_ERROR) {
failedFiles.push(file);
} else {
okFiles.push(file);
}
callback();
}, function(err) {
...
});
I am implementing the tutorial on the mean stack https://www.youtube.com/watch?v=AEE7DY2AYvI
I am adding a delete feature to remove items from the database on a button click
My client side controller has the following 2 functions to add to db and remove
$scope.createMeetup = function() {
var meetup = new Meetup();
meetup.name = $scope.meetupName;
meetup.$save(function (result) {
$scope.meetups.push(result);
$scope.meetupName = '';
});
}
$scope.deleteMeetup = function() {
item = $scope.meetups[0];
console.log("deleting meetup: " + item["name"]);
Meetup.delete(item);
scope.meetups.shift();
}
My server side has the following code
module.exports.create = function (req, res) {
var meetup = new Meetup(req.body);
meetup.save(function (err, result) {
res.json(result);
});
}
module.exports.remove = function(req, res) {
console.log("GOING TO REMOVE!!!");
console.log(req.query);
item = req.query;
Meetup.remove(item, function (err, results) {
console.log("done");
console.log(err);
console.log(results);
});
}
When I run my code and if I delete an already loaded item in the list, it is removed from Mongodb just fine. But if I add an item to the list and I do not refresh the page, it results in an error at my server that appears as
GOING TO REMOVE!!!
{ '$resolved': 'true',
__v: '0',
_id: '54ec04e70398fab504085178',
name: 'j' }
done
{ [MongoError: unknown top level operator: $resolved]
name: 'MongoError',
code: 2,
err: 'unknown top level operator: $resolved' }
null
I if I refresh the page, the it gets deleted fine. But if I added the entry, angular seems to be adding a new variable $resolved. Why is that happening?
Also another question, What is the proper way to call delete? I call it now but I am not able to put a callback. I want a callback which returns and then I shift the list of items. I tried adding a callback but the code never reaches it.
ie I tried the following
/*
Meetup.delete(item, function () {
console.log("In callback!!");
console.log(returnValue);
console.log(responseHeaders);
$scope.meetups.splice(item);
});
*/
/*Meetup.delete(item,
function (returnValue, responseHeaders) {
console.log("In callback!!");
console.log(returnValue);
console.log(responseHeaders);
$scope.meetups.splice(item);
},
function (httpResponse){
// error handling here
console.log("Need to handle errors");
});
*/
I am very new to node and am confused. Any help is very, very appreciated
Looks like it possible to call item.delete instead of Meetup.delete(item). You can call same methods on model instance. It prevent sending angular properties to server.
But better to make a rest API with delete method
DELETE /meetups/:id
and send just a _id
Meetup.remove({id: item._id});
I've come across this issue before with nested directives, but I managed to find a workaround there. I have code that looks a bit like,
var token = API.callGeneric({}, {method: 'kds.getTokenExtended2', params: ['demo', 'demo', '', '', '', '', '', false, '', '']}); //kds.
token.$promise
.then(function (result) {
if (!angular.isUndefined(result.error)) { // API error
$scope.msg = {iconClass: 'glyphicon-exclamation-sign', txt: 'Looks like there was a problem.'}
if (!APIErr.handle(result.error)) { // handle also returns continueExec flags
return;
}
}
$scope.msg = {iconClass: 'glyphicon-cloud-download', txt: 'almost there…'};
$scope.token = result.result;
console.log('result', result.result);
}, function (error) { // server error
$scope.msg = {iconClass: 'glyphicon-exclamation-sign', txt: 'issues with server, summoning the gods'}
APIErr.handle(error);
})
.then(function (result) {
$scope.msg = {}; // clear the message
// another api call to get bills
return API.callGeneric({}, {method: 'kds.getKitchenDisplayReceipts', params: [$scope.token, new Date().getTime()]});
}, APIErr.handle)
.then(function (result) {
console.log(result); // can see result.result.openReceipts
var receiptIds = result.result.openReceipts; // undefined?
}, APIErr.handle);
And API is a service that calls the API, obviously.
The problem is the last few lines, where console.log(result) shows result.result.openReceipts, and obviously result is a Resource object.
I'm stumped about what might be going on here. Any clues? How can I avoid this in future?
If you want to nest promises you need to return a promise every time.
Your second then is unnecessary in my opinion and could be done inside the first one as the first one is not returning any promises.
So it could be something like:
Pseudo-code:
API.call('token').then(function(result) {
...
return API.call('displayreceipts');
})
.then(function(result){
var recieptIds = result.result.openReceipts;
})
Let me know if it works.
UPDATE
Got it to work, but I still think there's a problem. I only get the correct return data if I set the setTimeout timer to be really long, like 2000. If I leave it at 200 then the callback function executes with empty data because the API call hasn't been returned yet.
I've updated the code below.
Setup:
I'm sending a get value from the front end via AJAX (jQuery) and then using that value to call the Foursqaure API to get a list of relevant venues back.
This is working "fine" except that the order of events get screwy. When I insert the GET value into the parameters for the function to evaluate, I'm getting a return value that I'm not asking for, which then causes the template to render on the front end before the other return value from my function – the one I want – is given.
And actually I don't think it's actually being returned. Just logged to the console.
Question:
How can I return the filtered list of JSON objects at the end of the initGetVenues function in places.js via AJAX to the front end?
Context:
I'm using this package to connect to Foursquare:
https://npmjs.org/package/foursquarevenues
AJAX call on front end
$("#search-items").submit(function() {
var placeQuery = $("#search-input").val();
$.ajax({
url: '/return/places/',
data: {"passPlaceQuery": placeQuery},
type: 'get',
dataType: 'html',
success: function(data) {
$("#search-results-list").html(data);
},
});
return false;
});
index.js [UPDATED]
returnPlaces: function(req, res) {
if (req.headers['x-requested-with'] === 'XMLHttpRequest') {
console.log("I've started routing");
return places.findVenue({
ll: "38.214986,-85.637054",
radius: 32186,
query: req.query.passPlaceQuery,
intent: "browse",
categoryId: "4bf58dd8d48988d1e0931735"
}, function(err, data) {
console.log("Venue callback");
if (err) {
res.send(500);
}
console.log("Attempting render: " + data);
return res.render("place-results", {
layout: false,
foundPlaces: data
});
});
} else {
return res.redirect("/");
}
}
places.js [UPDATED]
(function() {
var foursquare, initGetVenues;
foursquare = (require('foursquarevenues'))('SECRET', 'SECRET');
module.exports = {
findVenue: initGetVenues = function(criteria, callback) {
var jsonUniquePlaces;
jsonUniquePlaces = [];
foursquare.getVenues(criteria, function(error, venues) {
var i, objUniquePlace, range, uniquePlaces, venueName;
if (!error) {
range = Object.keys(venues.response.venues).length;
uniquePlaces = [];
i = 0;
while (i < range) {
venueName = venues.response.venues[i].name;
if (!(uniquePlaces.indexOf(venueName) > -1)) {
uniquePlaces.push(venueName);
}
i++;
}
i = 0;
while (i < uniquePlaces.length) {
objUniquePlace = {
place: uniquePlaces[i]
};
jsonUniquePlaces.push(objUniquePlace);
i++;
}
jsonUniquePlaces = JSON.stringify(jsonUniquePlaces);
return jsonUniquePlaces;
}
});
return setTimeout((function() {
return callback(null, jsonUniquePlaces);
}), 200);
}
};
}).call(this);
When setTimeout is 2000 I get:
| I've started routing
| [{"place":"Quills Coffee"},{"place":"Quills Coffe"},{"place":"Quill's Coffee"}]
| Venue callback
| Attempting render: [{"place":"Quills Coffee"},{"place":"Quills Coffe"},{"place":"Quill's Coffee"}]
| GET /return/places/?passPlaceQuery=quills 200 2009ms - 150
When setTimeout is 200 I get:
| I've started routing
| Venue callback
| Attempting render:
| GET /return/places/?passPlaceQuery=quills 200 210ms - 11
| [{"place":"Quills Coffee"},{"place":"Quills Coffe"},{"place":"Quill's Coffee"}]
You can't just return your value from inside findVenue. The call to foursquare.getVenues is asynchronous. So when the node engine gets to the function call foursquare.getVenues(opt, callback) it simply starts the operation and continues executing any further statements, then index.js continues executing, then you render a response... Finally some time later, the foursquare.getVenues code invokes its callback (presumably when it's done talking to the foursquare API.)
You need to rewrite places.findVenue to take a callback argument. When you call places.findVenue() you will pass it a function to execute as a callback. That is when you're supposed to send the response.
Here is a simplified example that you can hopefully extend:
function findVenue(opt, callback){
setTimeout(function(){
console.log('Done with foursquare.getVenues, calling back to routing function')
callback(null, opt);
// you passs this callback function to setTimeout. it executes it in 200ms
// by convention, pass a null error object if successful
}
,200);
};
app.get('/return/places', function(req, res){
console.log('routing function start');
findVenue({
lat:40,
lng: 70,
query: 'foo'
}, function(err, data){
console.log('findVenue callback');
if(err){ return res.send(500) };
res.render('template', {foo: data});
});
});