I am creating models in the for statement:
for (var j = 0; j < data.length; j++) {
models.MyModel1.create({ name : data[j].name },
function(err, model){
if (err) {
throw err
}
models.OtherMyModel.create({ model_id : model.id, index : j }],
function(err,submodule){
});
});
}
So here I want to create submodel that will use parent model id and it's index j. And because of async var j will be data.length - 1 for all callback. How I can pass index parameter to the model creation callback?
You can use promises to achieve that.
The following snippet uses when:
var when = require('when');
var nodefn = require('when/node/function');
var promises = [];
// Wrap model creation functions by a promise
var createMyModel1 = nodefn.lift(models.MyModel1.create);
var createOtherMyModel = nodefn.lift(models.OtherMyModel.create);
for (var j = 0; j < data.length; j++) {
// Store the index into a local variable because when the first promise
// will resolve, `i` would be equal to `data.length`!
var index = j;
// Create the first model
var promise = createMyModel1({
name: data[j].name
}).then(function(model) {
// Once the model is created, create the submodel
return createOtherMyModel({
model_id: model.id,
index: index
});
}, function(err) {
throw err;
});
// Store the promise in order to synchronize it with the others
promises.push(promise);
}
// Wait for all promises to be resolved
when.all(promises).then(function() {
console.log('All models are created');
});
Another solution is to use yield and any coroutine runner (available since node.js 0.11.x):
var co = require('co');
// Thunkify model creation functions
var createMyModel1 = function(data) {
return function(fn) { models.MyModel1.create(data, fn); };
};
var createOtherMyModel = function(data) {
return function(fn) { models.OtherMyModel.create(data, fn); };
};
co(function *() {
for (var j = 0; j < data.length; j++) {
// Create first model
var model = yield createMyModel1({
name: data[j].name
});
// Create its submodel
var submodel = yield createOtherMyModel({
model_id: model.id,
index: j
});
}
})();
Related
I'm a newbie in javascript and node js. I actually want to compare two data based on MySQL value.
what I want to do is Loop for the pair of data -> get value from db -> concat pair of data and value -> endloop
Here's my code
routes/masterdata.js
var Masterdata = require('../models/Masterdata');
var outputFile = {}
for (var i = 0; i < dataFile.length; i++) {
if (dataFile[i]['existing'] != null) {
for (var x = 0; x < dataFile.length; x++) {
var param = {
tech_row: dataFile[i]['existing'],
tech_col: dataFile[x]['new']
};
Masterdata.compareData(param, function(err, rows) {
console.log(rows);
outputFile.push({
value: rows
});
});
}
}
}
console.log(outputFile);
models/Masterdata.js
var Masterdata = {
compareData: function(param, callback) {
return db.query('SELECT value FROM sod_matrix WHERE TECH_NAME_ROW = ? AND TECH_NAME_COL = ?', [param.tech_row, param.tech_col], callback);
}
}
My Question is how to populate data from function compare data into an array in the loop?
var Promise = require('bluebird');
var completed=[];
var Masterdata = require('../models/Masterdata');
var outputFile = []; //should be an array
for (var i = 0; i < dataFile.length; i++) {
if (dataFile[i]['existing'] != null) {
for (var x = 0; x < dataFile.length; x++) {
var param = {
tech_row: dataFile[i]['existing'],
tech_col: dataFile[x]['new']
};
completed.push(new Promise(resolve, reject){
Masterdata.compareData(param, function(err, rows) {
if(err)
{
reject(err);
}
else
{
console.log(rows);
outputFile.push(rows); //that;s pretty enough
resolve();
}
});
});
}
}
}
Promise.all(completed)
.then((res)
{
console.log(outputFile);
});
So I have a longrunning query status page for my company, and it shows bars for the different database instances that change color based on number of longrunners + other criteria.
The issue is, every time the call is made to update the info, the colors all go back to default, and build from the ground up. This is because I'm using a $scope.variable object to hold the color information as the longrunner data is retrieved.
I want to switch this to using a local standard variable within the function, and only after all data has been retrieved, assign this variable to the $scope.variable.
Context - our instances are organized into swimlanes, so I create an object for the swimlane color and for the instance color. When all are collapsed, you only see the swimlane, so I needed a way for the instance color to bubble up to the swimlane.
So it amounts to something like this:
var getLongrunners = function(){
$scope.longrunnersByInstance = {};
for (var l = 0; l < $scope.swimlanes.length; l++){
$scope.slColor[$scope.swimlanes[l].swimlane] = 0;
}
for (var j = 0; j < instances.length; j++){
$scope.longrunnersByInstance[instances[j].instance] = [];
$scope.instanceColor[instances[j].instance] = 0;
}
for (var i = 0; i < instances.length; i++){
(function(e){
$http
.get('/getLongrunners',{params: {envFlag: '',instance: instances[e].instance}})
.then(function(response){
var longrunners = response.data;
for(var k = 0; k < longrunners.length; k++){
$scope.longrunnersByInstance[instances[e].instance].push(longrunners[k]);
}
if(longrunners.length > $scope.dangerThresh){
$scope.instanceColor[instances[e].instance] = 2;
}else if(longrunners.length >= $scope.warningThresh){
$scope.instanceColor[instances[e].instance] = 1;
}
if($scope.slColor[instances[e].swimlane] < $scope.instanceColor[instances[e].instance]) {
$scope.slColor[instances[e].swimlane] = $scope.instanceColor[instances[e].instance]
}
},getLongrunnersFail);
}(i));
So I want the $scope.slColor and $scope.instanceColor to be regular local variables until this loop finishes.
I look into promises, but that seemed to only be useful with $http before .then() is called on it.
Is there a way to make a custom promise type architecture and contain more than one function, and only return the promise when everything has been completed?
Thanks!
EDIT:
Most recent try:
var promises = [];
var longrunnersByInstance = {};
var instancesPerf = {};
var slColor = {};
var instanceColor = {};
var promiseTest = function() {
$scope.longrunnersByInstance = {};
for (var l = 0; l < $scope.swimlanes.length; l++){
slColor[$scope.swimlanes[l].swimlane] = 0;
}
for (var j = 0; j < instances.length; j++){
instanceColor[instances[j].instance] = 0;
}
instances.forEach(function (instance) {
promises.push($http
.get('/getLongrunners', {
params: {envFlag: 'bh', instance: instance.instance}
})
.then(function (response) {
var longrunners = response.data;
longrunnersByInstance[instance.instance] = [];
for (var k = 0; k < longrunners.length; k++) {
longrunnersByInstance[instance.instance].push(longrunners[k]);
}
if (longrunners.length > $scope.dangerThresh) {
instanceColor[instance.instance] = 2;
} else if (longrunners.length >= $scope.warningThresh) {
instanceColor[instance.instance] = 1;
}
console.log(instance.instance);
if (slColor[instance.swimlane] < instanceColor[instance.instance]) {
slColor[instance.swimlane] = instanceColor[instance.instance]
}
return true;
}, getLongrunnersFail)
);
function getLongrunnersFail(response){
console.log("getting longrunners failed" + response.status);
}
$q.all(promises).then(function () {
// longrunnersByInstance to $scope
console.log('calling all promises callback!');
instances.forEach(function (instance) {
$scope.longrunnersByInstance[instance.instance] = longrunnersByInstance[instance.instance];
});
// instancesPerf to $scope
instances.forEach(function (instance) {
$scope.instancesPerf[instance.instance] = instancesPerf[instance.instance];
});
// slColor to $scope
instances.forEach(function (instance) {
$scope.slColor[instance.instance] = slColor[instance.instance];
});
// instanceColor to $scope
instances.forEach(function (instance) {
$scope.instanceColor[instance.instance] = instanceColor[instance.instance];
});
}, allPromisesFail);
function allPromisesFail(){
console.log("all promises failed")
}
});
};
Angular uses the $q service for dealing with promises.
It has a function called all to deal with exactly the type of problem you encountered.
here is a simple fiddle to demonstrate it: http://jsfiddle.net/ThomasBurleson/QqKuk/
var myApp = angular.module('myApp', []);
function MyCtrl($scope, $q, $timeout) {
var thenFn = function(value){
console.log('resolved ', value);
return value;
},
q1 = $scope.q1 = $q.defer(),
q2 = $scope.q2 = $q.defer(),
p1 = $scope.q1.promise,
p2 = $scope.q2.promise;
$scope.fromThen = $q.all([
p1.then(thenFn),
p2.then(thenFn)
])
.then(function(values) {
console.log(values);
return values;
});
// Must start the AngularJS digest process
// to allow $q.resolve() to work properly
// So use $timeOut() or $apply()
setTimeout(function () {
$scope.$apply( function() {
console.log('resolving delayed promises');
q1.resolve({value : 1});
q2.resolve({value : 2});
});
}, 100, this);
/*
* Alternative approach
*
$timeout( function() {
console.log('resolving delayed promises');
q1.resolve({value : 1});
q2.resolve({value : 2});
});
*/
}
Here is how you would apply this to your code (haven't tested it, so it's just a direction, but it should get you going):
var promises = [];
for (var i = 0; i < instances.length; i++){
//$http return a promise, so you can just push it
promises.push( $http
.get('/getLongrunners',{params: {envFlag: '',instance: instances[e].instance}}));
}
$q.all(promises).then(function(values){
//values should contain an array with all the results you got from all the requests, so you can run through it and aggregate the results
});
Promises are chainable: when you return something inside the success callback of a promise you get a new promise that resolves with the returned value.
Example from angular documentation ("Chaining Promises" part):
promiseB = promiseA.then(function(result) {
return result + 1;
});
// promiseB will be resolved immediately after promiseA is resolved and its value
// will be the result of promiseA incremented by 1
So, in your /getLongRunners callbacks you can return a value that immediately resolves (like true) so that you get a promise that resolves as soon as the callback is done. If you collect all these "child" promises in an array you can than pass that array to $.all, and it will resolves when all the promises resolve, i.e. as soon as all the callbacks are done.
Here I replace the for loop and embedded immediately-executed function with the forEach method: it's clearer and avoids the closure problem you encountered
var promises = [];
instances.forEach(function(instance, i) {
promises.push($http
.get('/getLongrunners', {
params: {envFlag: '', instance: instances[e].instance}
})
.then(function(response) {
var longrunners = response.data;
// whatever you have to do
return true;
}, getLongrunnersFail);
});
$q.all(promises).then(function() {
// When you are here, all your callbacks will have been executed
});
I am trying to create an array from an asynchronous get request using a function that uses a for loop in order to pass a parameter in the get request.
var loadRarity = function () {
var rarities = [];
for (var i =0; i < deck.length; i++) {
Card.get({cardName: deck[i].card_name}, function(data) {
rarities.push(data.data[0].rarity);
console.log(rarities); //20 instances where the array is being populated
});
console.log(rarities);// result :20x [] empty array
}
return rarities;
};
var raritiesArray = loadRarity();
console.log(raritiesArray); //empty array
I can't figure out how to use the callback to make this work.
An option is to increment a counter to check if you are on the last callback an then do any needed operation in that last callback
var loadRarity = function () {
var rarities = [];
var counter = 0; // initialize counter
for (var i =0; i < deck.length; i++) {
Card.get({cardName: deck[i].card_name}, function(data) {
counter += 1; //increment counter
rarities.push(data.data[0].rarity);
console.log(rarities); //20 instances where the array is being populated
if(counter == deck.length){ //if true you are in the last callback
console.log(raritiesArray); // array with all the elements
}
});
}
return rarities;
};
var raritiesArray = loadRarity();
Waiting for all this async stuff to happen, your code that needs to use the result should be in its own callback, which runs when the result is available. For example:
var loadRarity = function(cb) {
var rarities = [],
counter = 0,
done = function(){
if(counter++ === deck.length - 1){
cb(rarities);
}
};
for (var i =0; i < deck.length; i++) {
Card.get({cardName: deck[i].card_name}, function(data) {
rarities.push(data.data[0].rarity);
done();
});
}
};
loadRarity(function(completedRarities){
console.log(completedRarities);
});
Sample (using an image onload fn to simulate your asysnc call): http://codepen.io/RwwL/pen/VeeEBR?editors=001
I am trying to return the array 'self.results' with all the arrays pushed in, which is after the self.yelpResults is completed. I want to use the returned array in another function. For now, self.parsedYelpArray is suppose to accept that array.
I am having trouble getting the self.results return all the arrays that are being pushed in. Instead, it asynchronously push the original empty array into the self.parsedYelpArray function.
How do I resolve this?
This is the code in my controller:
self.MapRouteArray = CompileMapArray.compileRoutes(data);
self.yelpResults = CompileYelpResults.compileYelp(self.MapRouteArray);
self.parsedYelpArray = ParsingYelpResults.parsingData(self.yelpResults);
And, these are the relevant services:
.service('CompileMapArray', function () {
var self = this;
self.MapRouteArray = [];
self.compileRoutes = function (data) {
for (var i = 0; i < data.response.route[0].leg[0].maneuver.length; i += 2) {
self.MapRouteArray.push(data.response.route[0].leg[0].maneuver[i].position.latitude + ',' + data.response.route[0].leg[0].maneuver[i].position.longitude);
}
return self.MapRouteArray;
};
})
.service('CompileYelpResults', function (YelpResource) {
var self = this;
self.results = [];
self.compileYelp = function (mapArray) {
for (var j = 0; j < mapArray.length; j++) {
YelpResource.getListings({term: self.yelpSearch, ll: mapArray[0]}, function (response) {
self.results.push(response.businesses);
console.log(self.results);
});
}
return self.results;
};
})
.service('ParsingYelpResults', function () {
var self = this;
self.parsingData = function (results) {
console.log(results);
};
});
You are trying to return from an asynchronous function; you'll always get unreliable results from that, you need to pass in a callback function that handles whatever operation you want at the end of your async... Like:
.service('CompileYelpResults', function (YelpResource) {
var self = this;
self.results = [];
self.compileYelp = function (mapArray, callbackFn) {
for (var j = 0; j < mapArray.length; j++) {
YelpResource.getListings({term: self.yelpSearch, ll: mapArray[0]}, function (response) {
self.results.push(response.businesses);
console.log(self.results);
});
}
callbackFn(self.results);
};
});
Then call the function with a callback function like so:
var parsed = CompileYelpResults.compileYelp(self.MapRouteArray, function(result) {
console.log(result);
});
This goes for all your asynchronous functions.
Relating to your comment the callback function you pass as second parameter to compileYelp takes the place of parsingData, so whatever processing you want to do with the results will be in the body of the callback function. It gives extra advantage in that you can use the results whichever way you like. For example.
var logged = CompileYelpResults.compileYelp(self.MapRouteArray, function(result) {
console.log(result);
});
var stringified = CompileYelpResults.compileYelp(self.MapRouteArray, function(result) {
JSON.stringify(result);
});
Below is a for loop which will run a max of time times, Inside of that for loop I make a GET call to return some data that needs to be added to my obj object.
I need some way to tell when all 3 GETS are finished as well as the for loop before calling the TagFactory.buildSavedView(obj) line. Thoughts?
for (var i = 0; i < termIDs.length; i++) {
ApiFactory.getTagData(tickers[i], termIDs[i]).then(function(data) {
singleTagArray.push(data.data.tickers);
var updatedTag = TagFactory.renderDirections(singleTagArray, null, period);
newTagObject = updatedTag[0];
tags.push(newTagObject);
finishObjSetup(tags);
console.log('viewHeaderDirect > obj: ', obj);
});
}
TagFactory.buildSavedView(obj);
vm.loadSavedModal = false;
You need to use $q.all, but creating a promise array and pass it to $q.all that will execute its .then only when all the promises gets executed.
Code
var promises = [];
for (var i = 0; i < termIDs.length; i++) {
var promise = ApiFactory.getTagData(tickers[i], termIDs[i]).then(function(data) {
singleTagArray.push(data.data.tickers);
var updatedTag = TagFactory.renderDirections(singleTagArray, null, period);
newTagObject = updatedTag[0];
tags.push(newTagObject);
finishObjSetup(tags);
console.log('viewHeaderDirect > obj: ', obj);
});
promise.push(promise); //creating promise array.
}
$q.all(promise).then(function(){
//here the call will goes after all calls completed.
})
You could use a simple counter:
var y = 0;
for (var i = 0; i < termIDs.length; i++) {
ApiFactory.getTagData(tickers[i], termIDs[i]).then(function (data) {
y++;
singleTagArray.push(data.data.tickers);
var updatedTag = TagFactory.renderDirections(singleTagArray, null, period);
newTagObject = updatedTag[0];
tags.push(newTagObject);
finishObjSetup(tags);
console.log('viewHeaderDirect > obj: ', obj);
if (y === termIDs.length) {
TagFactory.buildSavedView(obj);
vm.loadSavedModal = false;
}
});
}