How properly handle async functions in nodejs - javascript

I am new to nodejs, and I don't properly understand how async functions works. I read about them a lot today, but I cant solve my problem.
I use Sequelize.js as the ORM and my problem is when I nest a query into the callback of an other query then I cant force it to continues only when both query ended.
Here is my current code:
io.on('connection', function (socket) {
socket.on('join', function (data) {
clients[clients.length] = new Client("Client " + clients.length, data.channel);
console.log('Client connected Channel: ' + clients[clients.length-1].channel);
var array = []
DB.Matches.findAll({attributes: ['matchId', 'teamAId', 'teamBId']}).then(function (result) {
for (var i = result.length - 1; i >= 0; i--) {
DB.Teams.findAll({where: { team_id: [result[i].teamAId,result[i].teamBId]}}).then(function (teams) {
array.push({ id: 0, name: teams[0].clubName + ' - ' + teams[1].clubName});
}).then(function () {
// Now my emit event is here but I dont want to run every time the loop run
console.log(array);
socket.emit('matches', array);
});
}
}.then(function () {
// I tried to put it here, but then I got an empty array, because the queries haven't finshed yet
}));
});
});
When this code is called, the array will be emited in every loop with one more element in it in every loop, but this is not good for me. I want to call the emit event once when the array is totally filled.

The preferred way of solving this kind of thing is to use Promise.all
io.on('connection', function (socket) {
socket.on('join', function (data) {
clients[clients.length] = new Client("Client " + clients.length, data.channel);
console.log('Client connected Channel: ' + clients[clients.length-1].channel);
DB.Matches.findAll({attributes: ['matchId', 'teamAId', 'teamBId']}).then(function (result) {
var promises = [];
for (var i = result.length - 1; i >= 0; i--) {
promises.push(
DB.Teams.findAll({where: { team_id: [result[i].teamAId,result[i].teamBId]}}).then(function (teams) {
return { id: 0, name: teams[0].clubName + ' - ' + teams[1].clubName};
}));
}
Promise.all(promises).then(function(array) {
console.log(array);
socket.emit('matches', array);
});
});
});
});
edit:
If I understand you correctly you want to write
return { id: result[i].matchId, name: teams[0].clubName + ' - ' + teams[1].clubName};
But that doesn't work. That line of code is executed at some point in the future,
i.e. after the for loop has finished and by that time i is -1.
To make it work you need a new variable for each iteration of the loop.
You could do that e.g. by wrapping the code in another function like this
for(var i = result.length - 1; i >= 0; i--) {
(function(i) {
promises.push(
DB.Teams.findAll({where: { team_id: [result[i].teamAId,result[i].teamBId]}}).then(function (teams) {
return { id: result[i].matchId, name: teams[0].clubName + ' - ' + teams[1].clubName};
}));
})(i);
}
That way you use a different i variable (stored at a different place in memory) in each iteration.
But the best way to do it in this case is to use forEach. The only difference is that the loop will
iterate through the array forward and not backward as was the case with your for loop.
result.forEach(function(match) {
promises.push(
DB.Teams.findAll({where: { team_id: [match.teamAId,match.teamBId]}}).then(function (teams) {
return { id: match.matchId, name: teams[0].clubName + ' - ' + teams[1].clubName};
}));
});

Related

Need help retrieving items in multiple playlists

Got some code that works just fine and will retrieve all the items in a specified playlist but need to amend it so it can loop through an array of playlists and retrieve all items in each list.
I've tried putting for-next loops in various places in the code but, as my javascript is poor, those efforts have failed and I don't know what to do next.
function onGoogleLoad() {
showhide('hidden');
getSearchParameters();
gapi.client.setApiKey(APIKEY);
gapi.client.load('youtube', 'v3', function () {
GatherVideos("", function () {
for (var i = 0; i < allVideos.length; i++) {
console.log(allVideos[i].snippet.title + " published at " + allVideos[i].snippet.publishedAt)
}
showhide('visible');
build_html(allVideos);
});
});
}
ORIGINAL CODE ...
function GatherVideos(pageToken, finished) {
var request = gapi.client.youtube.playlistItems.list({
part: 'snippet, contentDetails',
playlistId: 'UU_FksrzP3q-IuoWTiG501LQ',
maxResults: 50,
pageToken: pageToken
});
request.execute(function(response) {
allVideos = allVideos.concat(response.items);
if (!response.nextPageToken)
finished();
else
GatherVideos(response.nextPageToken, finished);
});
}
END ORIGINAL CODE
NEW CODE WITH ATTEMPT AT LOOPING ...
function GatherVideos(pageToken, finished) {
for (var p=0;p<allPlaylists.length;p++)
{
console.log('Gathering: ' + allPlaylists[p]);
var request = gapi.client.youtube.playlistItems.list({
part: 'snippet, contentDetails',
playlistId: allPlaylists[p],
maxResults: 50,
pageToken: pageToken
});
request.execute(function(response) {
console.log('Executing: ' + request);
allVideos = allVideos.concat(response.items);
if (!response.nextPageToken)
finished();
else
GatherVideos(response.nextPageToken, finished);
});
} //End for loop
}
END NEW CODE ...
function build_html(parArray) {
var n = 0;
var playlistHtml = '';
var rows = Math.floor(parArray.length / vinrow);
var rem = (allVideos.length % vinrow);
if (rem > 0) {
rows++;
}
for (var i = 0; i < rows; i++) {
playlistHtml += '<div class="row">';
for (var k = 0; k < vinrow; k++) {
if (n < parArray.length) {
playlistHtml += '<div id=' + n + ' class="col item"><img class="img-responsive fit-image" src="' +
parArray[n].snippet.thumbnails.default.url + '"><div class="vtitle">' +
parArray[n].snippet.title + '</div></div>';
n++;
} else {
playlistHtml += '<div class="col item"><div class="vtitle"> </div></div>';
}
}
playlistHtml += "</div>";
}
playlist_div.innerHTML = playlistHtml;
}
}
So, need some help about where to place the code which will loop through the array of playlists.
You loop over allPlaylists, building the request and saving it in the request variable. The issue is, that this loops overwrites the request variable each time it is executed. When you later call request.execute(...) you're only executing the last request build (last playlist in the array).
You should move the request execution inside the for-loop.
for (var p = 0; p < allPlaylists.length; p++)
{
console.log('Gathering: ' + allPlaylists[p]);
var request = gapi.client.youtube.playlistItems.list({ /* ... */ });
request.execute(/* ... */);
}
The above already fixes part of the issue. This however doesn't fix the problem in its entirety. You're recursively calling GatherVideos (if there is more than 1 page) which in turn walks through the whole allPlaylists array again. Setting out new requests for each playlist.
To resolve the issue above retrieving videos from a single playlist should be moved into its own method. Using with the current structure is a bit cumbersome for different reasons, so I've rebuild it from scratch using a somewhat different approach. This might not be the exact answer you're looking for, but I hope it will give you some inspiration:
async function listPlaylist(options = {}) {
var maxResults, pendingMaxResults, response;
options = Object.assign({}, options);
maxResults = options.maxResults;
if (Number.isInteger(maxResults) && maxResults > 0) {
// set options.maxResults to a value 1-50
options.maxResults = (maxResults - 1) % 50 + 1;
pendingMaxResults = maxResults - options.maxResults;
} else if (maxResults === "all") {
pendingMaxResults = "all";
}
response = await Promise.resolve(gapi.client.youtube.list(options));
if (response.nextPageToken && (pendingMaxResults === "all" || pendingMaxResults > 0)) {
options.maxResults = pendingMaxResults;
options.pageToken = response.nextPageToken;
return response.items.concat(await listPlaylist(options));
} else {
return response.items;
}
}
(async function () {
var playlistsVideos, videos;
// retrieve all videos of all playlists
playlistsVideos = await Promise.all(allPlaylists.map(function (playlistId) {
return listPlaylist({
id: playlistId,
part: "snippet, contentDetails",
maxResults: "all"
});
}));
// the above variable `playlistsVideos` is in the format:
// [[p1v1, p1v2, p1v3], [p2v1, p2v2, p2v3]] (p = playlist, v = video)
// this needs to be flattened to have the result you want
videos = playlistsVideos.reduce((videos, playlistVideos) => videos.concat(playlistVideos), []);
})();
I'd recommend checking out the guide Using Promises and checking out the documentation for async/await. The above is based upon YouTube API V3. I hope I wrote the code clear enough to let it speak for itself. If you have any question just ask away in the comments.

how can start and stop an interval observable in RXJS?

I have a very simple timeInterval observable and I want to start/stop transmission without disconnecting subscribers (which should sit and wait regardless of observable status). Is possible, and if so how?
var source = Rx.Observable
.interval(500)
.timeInterval()
.map(function (x) { return x.value + ':' + x.interval; })
.take(10);
var subscription = source.subscribe(
function (x) {
$("#result").append('Next: ' + x + ' ');
},
function (err) {
$("#result").append('Error: ' + err);
},
function () {
$("#result").append('Completed');
});
general comment: most of the examples ive seen show how to define observables and subscribers. how do i affect the behavior of existing objects?
Depends on what is the source of the stop/resume signal. The simplest way I can think about is with the pausable operator, which as the documentation says works better with hot observables. So in the following sample code, I removed the take(10) (your pausable signal now comes through the pauser subject), and added share to turn your observable into a hot one.
About hot vs. cold, have a look to the illustrated respective data flows.
On subjects, you can also review the corresponding semantics
var pauser = new Rx.Subject();
var source = Rx.Observable
.interval(500)
.timeInterval()
.map(function (x) { return x.value + ':' + x.interval; })
.share()
.pausable(pauser);
var subscription = source.subscribe(
function (x) {
$("#result").append('Next: ' + x + ' ');
},
function (err) {
$("#result").append('Error: ' + err);
},
function () {
$("#result").append('Completed');
});
// To begin the flow
pauser.onNext(true); // or source.resume();
// To pause the flow at any point
pauser.onNext(false); // or source.pause();
Here is a more sophisticated example which will pause your source every 10 items:
// Helper functions
function emits ( who, who_ ) {return function ( x ) {
who.innerHTML = [who.innerHTML, who_ + " emits " + JSON.stringify(x)].join("\n");
};}
var pauser = new Rx.Subject();
var source = Rx.Observable
.interval(500)
.timeInterval()
.map(function (x) { return x.value + ':' + x.interval; })
.share();
var pausableSource = source
.pausable(pauser);
source
.scan(function (acc, _){return acc+1}, 0)
.map(function(counter){return !!(parseInt(counter/10) % 2)})
.do(emits(ta_validation, 'scan'))
.subscribe(pauser);
var subscription = pausableSource.subscribe(
function (x) {
$("#ta_result").append('Next: ' + x + ' ');
},
function (err) {
$("#ta_result").append('Error: ' + err);
},
function () {
$("#ta_result").append('Completed');
});
You should have by now your answer to the second question. Combine the observables you are given with the relevant RxJS operators to realize your use case. This is what I did here.
not the most elegant, but probably the simplest:
timeSubscription: Subscription
timer: Observable<number>;
time = 0;
toggle() {
if (!this.timer)
this.timer = interval(500);
if (!this.timeSubscription || this.timeSubscription.closed)
this.timeSubscription = this.timer.subscribe(tick => { // running
console.log(this.time++);
});
else
this.timeSubscription.unsubscribe(); // not running
}

unable to parse users query results using titanium cloud

I am performing a query on users(titanium cloud service) as such:
Cloud.Users.query({
page: 1,
per_page: 5,
where: {
email: 'example#example.com'
}
}, function(e) {
if (e.success) {
alert(JSON.parse(e.users));
} else if (e.error) {
alert(e.message);
} else {}
});
After executing the query I am unable to parse e.users on success, alert returns nothing. Any thoughts?
From what I understand from this example in Titanium docs, e.users is an array, not a JSON string. You should be able to do something like:
for (var i = 0; i < e.users.length; i++) {
var user = e.users[i];
alert('id: ' + user.id + '\n' +
'first name: ' + user.first_name + '\n' +
'last name: ' + user.last_name);
}
You're trying to parse e.users which is an array. You should traverse through the array using a loop and you can simply alert each user using JSON.stringify method
Try the following code
Cloud.Users.query({
page: 1,
per_page: 5,
where: {
email: 'example#example.com'
}
}, function (e) {
if (e.success) {
alert('Total Users: ' + e.users.length);
for (var i = 0; i < e.users.length; i++) {
var user = e.users[i];
alert(JSON.stringify(user)); //This line will display the details of the user as a string
}
} else {
alert('Error:\n' +
((e.error && e.message) || JSON.stringify(e)));
}
});
You should read Titanium.Cloud.Users module. Documentation itself shows how to query users.

Issue while getting array value in NodeJS

I am using NodeJS to count the number of employees in different section. I am using Mongoose as ODM and MongoDB as database.That is my code (very simple for testing purposes).
exports.list= function( req, res){
var array = ['sectionA', 'sectionB'];
var i;
for(i = 0; i<array.length; i++){
Issue.count({ 'section.name': array[i]}, function (err, count) {
console.log('Section name is: ' + array[i] + ' number of employees: ' + count );
)};
}
}
But the value of array[i] is undefined inside Issue.count({ 'section.name': array[i]}, function (err, count) {});. But the value of count is absolutely right. I want an output like:
Section name is: sectionA number of employees: 50
Section name is: sectionB number of employees: 100
But my current output is
Section name is: undefined number of employees: 50
Section name is: undefined number of employees: 100
This is because value of i inside Issue.count({ 'section.name': array[i]}, function (err, count) {}); is always 2.
Is it possible that Issue.count function is asynchronous? So your loop is completing before the callback of:
function (err, count) {
console.log('Section name is: ' + array[i] + ' number of employees: ' + count );
}
is executed. When the callbacks are executed the value of i is undefined as a result.
#eshortie is correct: Issue.count is asynchronous and that's causing the problem.
Here's a solution:
for (i = 0; i<array.length; i++) {
Issue.count({ 'section.name': array[i]}, function(sectionName, err, count) {
console.log('Section name is: ' + sectionName + ' number of employees: ' + count );
}.bind(null, array[i]));
}
Don't try to execute asynchronous functions using a regular for loop. It is asking for problems. Use async.eachSeries or async.each instead https://github.com/caolan/async#eachseriesarr-iterator-callback
var async = require('async')
var Issue = {} // mongoose isue model here
var elements = ['foo', 'bar']
async.eachSeries(
elements,
function(name, cb) {
var query = {
'section.name': name
}
Issue.count(query, function(err, count) {
if (err) { return cb(err) }
console.dir(name, count)
})
},
function(err) {
if (err) {
console.dir(err)
}
console.log('done getting all counts')
}
)
Using Q library
var Q = require('q')
var i = 0;
function hello (item){
var defer = Q.defer();
Issue.count({'section.name': student}, function (err, count) {
if(err){
defer.reject(err);
}else{
var result = 'Section name is: ' + item + ' number of employees: ' + count ;
defer.resolve(result)
}
});
})
return defer.promise;
}
function recall(){
hello(checkItems[i]).then((val)=>{
console.log(val)
if(i < checkItems.length - 1){
i++
recall();
}
})
}
recall()

Synchronous Json calls from javascript

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

Categories