I'm trying to use the best practics with Javascript but it's so hard for me.In this case I wanna use some function "avoid" the callbacks. I'm trying to use the Deferred object of jquery to do it. So I believe that I don't how to works fine.
I'm working with phonegap + emberjs + cordova-sqlite-plugin.
I've the follwing function that implements callbacks.
getPuntos: function(callback)
{
var db = window.sqlitePlugin.openDatabase("Rondas", "1.0", "Dev", -1);
var ret ={data:false};
db.transaction(function(tx)
{
tx.executeSql("SELECT * from Punto;",[], function(tx, res)
{
if( res.rows.length !== 0)
{
var puntos = [];
for( var i=0; i<res.rows.length; i++)
{
var descripcion = res.rows.item(i).descripcion;
var id = res.rows.item(i).id;
//console.log( descripcion );
var punto = App.Punto.create( { index: i, id:id, descripcion: descripcion});
puntos.push( punto );
}
ret.data = puntos;
callback(ret.data);
}
});
},function(tx,err){
console.log('Error SQL'+err);
},function(){
console.log( 'Base de datos abierta' );
});
}
and I use this as well:
this.getPuntos(function(puntos){
for( var i=0; i<puntos.length; i++){
console.log( puntos[i] );
}
});
Well, but I try to implements something very clear and easy like:
//whitout use a callback.
var puntos = this.getPuntos();
for( var i=0; i<puntos.length; i++){
console.log( puntos[i] );
}
Then I try to do it:
Change the function getPuntos to _getPuntos, this function has a deferred obeject.
the function getPuntos is wrapper to handle the result of promise getting in the method _getPuntos and try to return.(but not works)
_getPuntos: function()
{
var db = window.sqlitePlugin.openDatabase("Rondas", "1.0", "Dev", -1);
var deferred = jQuery.Deferred();
var ret ={data:false};
db.transaction(function(tx)
{
tx.executeSql("SELECT * from Punto;",[], function(tx, res)
{
if( res.rows.length !== 0)
{
var puntos = [];
for( var i=0; i<res.rows.length; i++)
{
var descripcion = res.rows.item(i).descripcion;
var id = res.rows.item(i).id;
//console.log( descripcion );
var punto = App.Punto.create( { index: i, id:id, descripcion: descripcion});
puntos.push( punto );
}
//ret.data = puntos;
//callback(ret.data);
deferred.resolve(puntos);
}
});
},function(tx,err){
console.log('Error SQL'+err);
},function(){
console.log( 'Base de datos abierta' );
});
return deferred.promise();
},
The wrapper:
getPuntos: function()
{
var promise = this._getPuntos();
var ret = {data:false};
promise.done(function(result)
{
ret.data = result;
return ret.data;
});
while(ret.data ===false ){} //wait for the result until it's available
return ret.data;
},
In the main function I call it :
var puntos = this.getPuntos();
console.log( puntos+"--shoulbe [object]"); //I get Undefined or false, but no my array.
So, is there some way to do that I wanna, convert a asynchronous function in synchronous
function?.
Thanks for all your answers.
The main problem here is that your getPuntos() function in the second example does not return anything hence the null/false value.
getPuntos: function()
{
var promise = this._getPuntos();
var ret = {data:false};
promise.done(function(result)
{
// This happens in the future
ret.data = result;
return ret.data;
});
// This happens now and there is nothing returned
},
When your promise is completed (promise.done) it returns ret.data but this is returned to the promise and is ignored. It is in the wrong scope. You thinking in a traditional 'pull' model where data is pulled towards you and the program blocks until data is available. Javascript uses callbacks and is more of a 'push' model where data is pushed onto callbacks when it is available and there is no blocking.
You need to rearrange things. For example the getPuntos function should return the promise.
getPuntos: function()
{
var promise = this._getPuntos();
return promise;
},
And the main function should add a callback for when the promise is fulfilled.
var puntos = this.getPuntos();
puntos.done(function(result) {
console.log( result+"--shoulbe [object]");
});
Although the getPuntos() function is a bit redundant here as it is just returning the value of _getPuntos().
The problem is that your getPuntos() function sets up the promise.done action, but the returned value never gets passed to the rest of your program. As NoxHarmonium said, your getPuntos() method is superfluous and should be combined with _getPuntos(), but you probably want something like the following (as you can see in this jsFiddle):
PuntosPromise = function() {
this.result = null;
this._getPuntos = function() {
var deferred = $.Deferred();
setTimeout(function(scope) {
scope.result = "It works."
deferred.resolve();
}, 500, this);
return deferred.promise();
}
this.getPuntos = function()
{
var promise = this._getPuntos();
return promise;
}
this.getResults = function() {
return this.result;
};
}
$("#tester").click(function() {
var test = new PuntosPromise();
$.when(test.getPuntos()).then(function() {
alert(test.getResults());
});
});
EDIT:
See #Bergi's modifications given below. While this code works, his solution is cleaner.
Related
I'm having trouble figuring out why my data is not being push into my new array, "results". newArr[0].mscd.g[i] is a list of several objects.
var axios = require('axios');
var moment = require('moment');
var _ = require('lodash');
var getData = function() {
return getNBASchedule().then(function(payload) {
return filterByMonth('January', payload);
}).then(function(result) {
return result
});
}
....
getData grabs the data from baseURL and returns a list of objects.
var getMonthlySchedule = function(data){
var results = [];
var newArr = data.slice(0, data.length);
for (var i = 0; i <= newArr[0].mscd.g.length; i++) {
if (newArr[0].mscd.g[i].v.tid === 1610612744 || newArr[0].mscd.g[i].h.tid === 1610612744) {
results.push(newArr[0].mscd.g[i]); <---- //does not seem to work
// however if I were to console.log(newArr[0].mscd.g[i],
// I would see the list of objects)
}
}
return results; <-- //when i console at this point here, it is blank
};
var getSchedule = function () {
return getData().then(function(pl) {
return getMonthlySchedule(pl)
})
};
var monthlyResults = function() {
return getSchedule().then(function(r) {
console.log("result", r)
return r
});
};
monthlyResults();
You don't know when getSchedule() is done unless you use a .then() handler on it.
getSchedule().then(function(data) {
// in here results are valid
});
// here results are not yet valid
You are probably trying to look at your higher scoped results BEFORE the async operation has finished. You HAVE to use .then() so you know when the operation is done and the data is valid.
Your code should simplify as follows :
var getData = function() {
return getNBASchedule().then(function(payload) {
return filterByMonth('January', payload);
});
}
var getMonthlySchedule = function(data) {
return data[0].mscd.g.filter(function(item) {
return item.v.tid === 1610612744 || item.h.tid === 1610612744;
});
};
var monthlyResults = function() {
return getData()
.then(getMonthlySchedule)
.then(function(r) {
console.log('result', r);
return r;
});
};
monthlyResults();
This may fix the problem. If not, then :
Check the filter test. Maybe those .tid properties are String, not Number?
Check that data[0].mscd.g is the right thing to filter.
I am trying to write a loop which performs a number of http requests and adds each response to a list.
However, I don't think I am going about it quite the right way.
I think I am not implementing the required promises correctly. The console log after the for loop shows myList array as empty.
Code:
var _myList = []
function getStuff() {
var deferred = $q.defer()
var url = someUrl
$http.get(url).success(function(response) {
if ( response.array.length > 0 ) {
// loop starts here
for ( var i=0; i < response.array.length; i++ ) {
getThing(response.array[i].id);
};
// check the varibale here
console.log(_myList);
deferred.resolve('Finished');
} else {
deferred.resolve('No stuff exists');
};
}).error(function(error) {
deferred.reject(error);
});
return deferred.promise;
};
function getThing(thindId) {
var deferred = $q.defer()
var url = someUrl + thingId;
$http.get(url).success(function(response) {
_myList.push(response);
deferred.resolve(response);
}).error(function(error) {
deferred.reject(error);
});
return deferred.promise;
};
You can simplify your code as follows:
var allThings = response.array.map(function(id){
var singleThingPromise = getThing(id);
//return a single request promise
return singleThingPromise.then(function(){
//a getThing just ended inspect list
console.log(_myList);
})
});
$q.all(allThings).then(function(){
//only resolve when all things where resolved
deferred.resolve('Finished');
}, function(e){
deferred.reject('Something went wrong ' + e);
});
You indeed won't be able to populate _myList array with for-loop like you set up. Instead create an array of promises - one per data item in response.array and return it as inner promise.
function getStuff() {
var url = someUrl;
return $http.get(url).then(function(response) {
if (response.data.array.length > 0) {
return $q.all(response.data.array.map(function(data) {
return getThing(data.id);
}));
} else {
return 'No stuff exists';
}
});
}
function getThing(thindId) {
var url = someUrl + thingId;
return $http.get(url).then(function(response) {
return response.data;
});
}
After that you would use getStuff like this:
getStuff().then(function(myList) {
console.log(myList);
});
Similar to this question : Return response from async call
Except that the call is within a loop that calls multiple time the asynchronous function.
Specifically, how can the value of s be returned? This code returns undefined. This function is called within a for loop. The library used for ORM is Bookshelfjs. Thanks for the help.
function getUsernameFromDBAsync(userId) {
var s = "moo";
new Model.Users({
idUser: userId
})
.fetch()
.then(function(u) {
var prenom = u.get('firstName');
var nom = u.get('familyName');
s = prenom + " " + nom;
return s;
});
}
Your aren't really showing how you're doing the loop which makes it a little harder to guess what to recommend. But assuming .fetch().then() returns a promise, here's a general idea with standard ES6 promises built into node.js:
function getUsernameFromDBAsync(userId) {
var s = "moo";
return new Model.Users({
idUser: userId
}).fetch().then(function (u) {
var prenom = u.get('firstName');
var nom = u.get('familyName');
s = prenom + " " + nom;
return s;
});
}
var userIds = [...];
var promises = [];
for (var i = 0; i < userIds.length; i++) {
promises.push(getUsernameFromDBAsync(userIds[i]));
}
// now set up a .then() handler for when all the promises are done
Promise.all(promises).then(function(names) {
// process names array here
}, function(err) {
// process error here
});
If you are using the Bluebird promise library, you could do it a little bit more simply like this:
function getUsernameFromDBAsync(userId) {
var s = "moo";
return new Model.Users({
idUser: userId
}).fetch().then(function (u) {
var prenom = u.get('firstName');
var nom = u.get('familyName');
s = prenom + " " + nom;
return s;
});
}
var userIds = [...];
Promise.map(userIds, getUsernameFromDbAsync).then(function(names) {
// process names array here
}, function(err) {
// process error here
});
Var s is unnecessary. Simply return the "prénom nom" string from then's success callback.
function getUsernameFromDBAsync(userId) {
return new Model.Users({
idUser: userId
}).fetch().then(function (u) {
return u.get('firstName') + ' ' + u.get('familyName');
});
}
Your "loop" can be achieved with Array.prototype.map() to generate an array of promises, followed by Promise.all(promises).then(...), to receive and handle an array of names when all the promises have resolved.
var promises = userIds.map(function(userId) {
return getUsernameFromDBAsync(userId);
});
Promise.all(promises).then(function(names) {
// do something with `names`, which is an array of `prénom nom` strings.
}, function(err) {
// handler error, eg ...
console.error(error);
});
Or more succinctly :
Promise.all(userIds.map(getUsernameFromDBAsync)).then(function(names) {
// do something with `names`, which is an array of `prénom nom` strings.
}, function(err) {
// handler error, eg ...
console.error(error);
});
How do I use defer correctly? I've got two functions here, in which one defer is used correctly? In which one it is used incorrectly? And why resp. why not?
First example:
getFoo1: function() {
var dfd = $q.defer();
db.allDocs({include_docs: true}, function(err, response) {
if(err) {
console.log(err);
dfd.reject(err);
} else {
var qcs = [];
for(var i=0; i < response.total_rows; i++) {
if(response.rows[i].doc.type == 'bar') {
var qc = {id: response.rows[i].doc._id,
isFoo: true
};
oneFunction(qc)
.then(anotherFunction(qc))
.then(qcs.push(qc));
}
}
dfd.resolve(qcs);
}
});
return dfd.promise;
},
Second example:
getFoo2: function() {
var dfd = $q.defer();
db.allDocs({include_docs: true}, function(err, response) {
if(err) {
dfd.reject(err);
} else {
dfd.resolve(response);
}
});
return dfd.promise
.then(function(response) {
var foo = [];
for(var i=0; i < response.total_rows; i++) {
if(response.rows[i].doc.type == 'bar') {
var qc = {id: response.rows[i].doc._id,
isFoo: true
};
return oneFunction(qc)
.then(anotherFunction(qc))
.then(foo.push(qc));
}
}
}, function(err){
console.log(err);
});
},
The oneFunction does nothing asynchronously.
The anotherFunction does something asynchronously (retrieving data from an external database).
EDIT: Thanks to #Bergi the correct function should look like this:
getFoo3: function() {
var dfd = $q.defer();
db.allDocs({include_docs: true}, function(err, response) {
if(err) {
dfd.reject(err);
} else {
dfd.resolve(response);
}
});
return dfd.promise
.then(function(response) {
var foos = [];
for (var i=0; i < response.total_rows; i++) {
if (response.rows[i].doc.type == 'bar') {
var qc = {id: response.rows[i].doc._id,
isFoo: true
};
var foo = oneFunction(qc);
foos.push(foo);
}
}
var promises = foos.map(anotherFunction); // make a promise for each foo
return $q.all(promises);
}, function(err){
console.log(err);
});
},
You've used $q.defer correctly in the second example[1] - creating a promise for the response of the db.allDocs asynchronous call (and nothing else). Altough pochdb seems to already return promises, as #Benjamin mentions in the comments, so it's unnecessary (but not wrong).
The first example is just a mess, convoluting the construction of the promise with the error logging and that ominous loop.
1: Except for dfd.promise(), which is not a function but a property. Go for dfd.promise.then(…).
However, that loop is a very different topic, and seems to be wrong in both snippets. Some points:
In the second snippet, your return from the callback function in the body of the loop, right on the first iteration that fulfills the predicate.
If oneFunction(qc) is not asynchronous, it doesn't need to (read: shouldn't) return a promise - or if it does not, that .then(…) call is wrong.
anotherFunction(qc) seems to return a promise (it's asynchronous as you say). But you must not pass that promise to .then(), a callback function is expected there!
Same thing for foo.push(qc) - it's not a callback function that you would pass to .then().
After all, you are doing something asynchronous in that loop. Here, you have to use Promise.all now!
If I had to bet, I'd say you need
.then(function(response) {
var foos = [];
for (var i=0; i < response.total_rows; i++) {
if (response.rows[i].doc.type == 'bar') {
var qc = {id: response.rows[i].doc._id,
isFoo: true
};
var foo = oneFunction(qc);
foos.push(foo);
}
}
var promises = foos.map(anotherFunction); // make a promise for each foo
return $q.all(promises);
})
I got a question regarding the solr-client module of nodejs. I'm using this module for querying against a solr-index.
The module itself works fine as long as I don't have to wait for finishing of the query and as long I need the result only as a async result.
But currently I cannot find out, how I will be able to await the finishing of a search request and use the result in a sequential way.
I have the follwing method in my manager
SolrManager.prototype.promisedQuery = function(query, callback) {
var solrClient = solr.createClient(this.configuration.cores.page);
var docs = null;
var finished = false;
var deferred = Q.defer();
var request = solrClient.search(query, function(err,obj){
if (!err) {
if (obj.response.numFound > 0) {
deferred.resolve(obj.response.docs);
} else {
deferred.resolve(null);
}
} else {
deferred.reject(err);
}
});
var records = null;
var promise = deferred.promise;
promise.then(function(result) {
records = result;
}).fail(function(error){
records = error;
});
return records;
};
The problem here is, that I try to wait for the result of the query and use it as return value of "promisedQuery".
I try since days to use this method in a sequential call, also with different additional modules like "wait.for", "q", etc. but nothing seems to work.
The callback function of the solr-client will always be executed after the manager-method has already returned. Also the promise-methods will be even called after the return from the manager-method.
Can someone help me out on that topic or have some tips, how I can await the response of the solr-client-search operation and then give it back in a sequential way?
Thanks for any help.
Udo Gerhards
over one week, it seems now that I have found a solution:
SolrManager.prototype.promisedQuery = function(query, callback) {
var solrClient = solr.createClient(this.configuration.cores.page);
var docs = null;
var deferred = Q.defer();
var request = solrClient.search(query, function(err,obj){
if (!err) {
if (obj.response.numFound > 0) {
deferred.resolve(obj.response.docs);
} else {
deferred.resolve(null);
}
} else {
deferred.reject(err);
}
});
return deferred.promise;
};
in all other managers, which are calling the above function:
...
var dbPromise = this.solrManager.promisedQuery(query);
var _self = this;
return Q.async(function*(){
var result = yield dbPromise;
return result;
});
...
After first tests, it seems that synchronized methods will wait until the promise is settled.
The only thing is, that it runs only with NodeJs version 0.11.10, which supports generator functions, with activated --harmony-flag and "q"-module.
Best regards
Udo
You are just using the promises a bit incorrectly. Instead of returning records, you need to return 'deferred.promise'. It should look something like this (note that you don't need the callback you passed into promisedQuery).
SolrManager.prototype.promisedQuery = function(query) {
var solrClient = solr.createClient(this.configuration.cores.page),
deferred = Q.defer();
solrClient.search(query, function(err,obj){
if (!err) {
if (obj.response.numFound > 0) {
deferred.resolve(obj.response.docs);
} else {
deferred.resolve(null);
}
} else {
deferred.reject(err);
}
});
return deferred.promise;
};
To use it you would do something like:
SolrManager.promisedQuery(myquery)
.then(function (data) {
// data is whatever your 'resolved' in promisedQuery
}, function (err) {
// err is whatever you rejected in promisedQuery
});
based on rquinns answer I've changed the code like follows:
SolrManager.prototype.promisedQuery = function(query, callback) {
var solrClient = solr.createClient(this.configuration.cores.page);
var docs = null;
var finished = false;
var deferred = Q.defer();
var request = solrClient.search(query, function(err,obj){
if (!err) {
if (obj.response.numFound > 0) {
deferred.resolve(obj.response.docs);
} else {
deferred.resolve(null);
}
} else {
deferred.reject(err);
}
});
return deferred.promise;
};
...
DemoObject.prototype.toString = function() {
return SolrManager.promisedQuery(this.query).then(function(result){
return result['title'];
}).fail(function(error){
return error;
});
};
DemoObject.prototype.typeOf = function() {
return SolrManager.promisedQuery(this.query).then(function(result){
return result['title'];
}).fail(function(error){
return error;
});
};
I think, this is the right way to use the "promise"-object. But what happens when i do the follwing:
...
var demoObject = new DemoObject();
demoObject.query = "id:1";
console.log(''+demoObject);
...
or if I use "demoObject" by concatenating it to a string
...
var string = "Some string "+demoObject;
...
In case of the string concatenation, I'm currently not sure that the string will contain also the title field from the database. Same for console output.
Will nodejs be so intelligent that it resolves for e.g. the string concatenation "after" the results from the database will be available?
BR
Udo