I have a NodeJS script that I have been working on, but the biggest problem I have is that chaining all of these promises is not only ugly to look at, but difficult to maintain as time goes on.
I want to convert these individual promises into one, using the Promise.all() method, but am not sure how I can get the same functionality and assign variables from one promise to another using this method.
For example, my second promise: methods.login() returns a sessionId that is used in almost every other promise. How would I assign that variable and pass it to further dependent promises using Promise.all()??
Here is my current code:
var zabbixApi = require('./zabbixapi.js');
var methods = require('./methods.js');
var fs = require('fs');
var SESSIONID;
function main() {
var apiVersion, loggedOut, numTemplates, numHosts, numHostGroups, numItems;
var templates = []
, hostGroups = []
, hosts = [];
/*var promises = [];
promises.push(zabbixApi(methods.getApiVersion()));
promises.push(zabbixApi(methods.login(SESSIONID)));
promises.push(zabbixApi(methods.getHostGroups(SESSIONID)));
promises.push(zabbixApi(methods.getTemplates(SESSIONID)));
promises.push(zabbixApi(methods.getHosts(SESSIONID)));
// promises.push(zabbixApi(methods.configExport(hostIds, templateIds, groupIds, SESSIONID)));
promises.push(zabbixApi(methods.logout(SESSIONID)));
Promise.all(promises).then(function (values) {
console.log('All promises completed.');
}, function (reason) {
console.log('Error completing promises: ' + reason);
});*/
// Get API version
zabbixApi(methods.getApiVersion())
// If successful, login to the API
.then(function (version) {
apiVersion = version.result;
// Verify that the API version returned is correct
if (apiVersion.length < 5 || !apiVersion) {
console.log('Error occurred retrieving API version: ' + version.error.data);
return 1;
} else {
return zabbixApi(methods.login(SESSIONID));
}
}, function (error) {
console.log('Error occurred retrieving API version: ' + error);
return 1;
// If login successful, continue operations until logged out or error
}).then(function (auth) {
SESSIONID = auth.result;
if (!SESSIONID) {
console.log('Error retrieving session id: ' + auth.error.data);
return 1;
} else {
console.log('Logged in successfully!');
return zabbixApi(methods.getHostGroups(SESSIONID));
}
}, function (error) {
console.log('Error occurred authenticating: ' + error);
return 1;
// Attempt to retrieve all hostgroup information
}).then(function (hostgroups) {
numHostGroups = hostgroups.result.length;
hostGroups = hostgroups.result;
if (!numHostGroups) {
console.log('Error occurred retrieving host groups: ' + hostgroups.error.data);
return 1;
} else {
return zabbixApi(methods.getTemplates(SESSIONID));
}
}, function (error) {
console.log('Error occurred retrieving host groups: ' + error);
return 1;
// Attempt to retrieve host information
}).then(function (template) {
numTemplates = template.result.length;
templates = template.result;
if (!numTemplates) {
console.log('Error occurred retrieving templates: ' + template.error.data);
return 1;
} else {
return zabbixApi(methods.getHosts(SESSIONID));
}
}, function (error) {
console.log('Error occurred retrieving templates: ' + error);
return 1;
// Attempt to retrieve host information
}).then(function (hosts) {
numHosts = hosts.result.length;
hosts = hosts.result;
if (!numHosts) {
console.log('Error occurred retrieving host groups: ' + hosts.error.data);
return 1;
} else {
var groupIds = []
, hostIds = []
, templateIds = [];
// Extract all groupIds for host groups
for (var i = 0; i < numHostGroups; i++) {
groupIds[i] = hostGroups[i].groupid;
}
// Extract all hostIds for hosts
for (var i = 0; i < numHosts; i++) {
hostIds[i] = hosts[i].hostid;
}
// Extract all templateIds for templates
for (var i = 0; i < numTemplates; i++) {
templateIds[i] = templates[i].templateid;
}
return zabbixApi(methods.configExport(hostIds, templateIds, groupIds, SESSIONID));
}
}, function (error) {
console.log('Error occurred retrieving host groups: ' + error);
return 1;
// Attempt to retrieve configuration information
}).then(function (config) {
//console.log(config);
if (config.error) {
console.log('Error occurred retrieving configuration: ' + config.error.message + ': ' + config.error.data);
return 1;
} else {
if (!writeToFile(config)) {
return 1;
} else {
console.log('Configuration details exported successfully.');
}
return zabbixApi(methods.logout(SESSIONID));
}
}, function (error) {
console.log('Error occurred retrieving configuration: ' + error);
return 1;
// Attempt to logout of API, if logout successful exit safely
}).then(function (logout) {
loggedOut = logout.result;
if (!loggedOut) {
console.log('Error logging out: ' + logout.error.data);
return 1;
} else {
console.log('Logged out successfully!');
return 0;
}
}, function (error) {
console.log('Error occurred logging out: ' + error);
return 1;
});
}
function writeToFile (config) {
fs.writeFile('zabbix_config_export.json', JSON.stringify(config), function (err) {
if (err) {
return console.log('Error writing configuration to file: ' + err);
}
});
return true;
}
main();
You can use Promise.all() on operations that can run in parallel, but not on operations that must be performed in a specific sequence.
In looking at your code, it appears that you do have a couple places where you can do some operations in parallel, but not all your operations can be done that way. It appears you can do things in this general sequence:
getApiVersion
Login to session
In parallel (getHostGroups, getTemplates, getHosts)
configExport previous results
In parallel (logout, writeToFile)
That can be achieved like this:
var zabbixApi = require('./zabbixapi.js');
var methods = require('./methods.js');
var fs = require('fs');
var SESSIONID;
function logout() {
if (SESSIONID) {
var p = zabbixApi(methods.logout(SESSIONID));
// clear SESSIONID to show that we've already launched a logout attempt, no need to try again
SESSIONID = null;
return p;
} else {
return Promise.resolve();
}
}
function main() {
var apiVersion, hostGroups, templates, hosts;
// Get API version
zabbixApi(methods.getApiVersion())
// If successful, login to the API
.then(function (version) {
apiVersion = version.result;
// Verify that the API version returned is correct
if (!apiVersion || apiVersion.length < 5) {
throw new Error('Error occurred retrieving API version: ' + version.error.data);
} else {
return zabbixApi(methods.login(SESSIONID));
}
}, function (error) {
throw new Error('Error occurred retrieving API version: ' + error);
// If login successful, continue operations until logged out or error
}).then(function (auth) {
SESSIONID = auth.result;
if (!SESSIONID) {
throw new Error('Error retrieving session id: ' + auth.error.data);
} else {
console.log('Logged in successfully!');
// now that we are logged in, a number of operations can be launched in parallel
return Promise.all([
zabbixApi(methods.getHostGroups(SESSIONID),
zabbixApi(methods.getTemplates(SESSIONID),
zabbixApi(methods.getHosts(SESSIONID)
]);
}
}, function (error) {
throw new Error('Error occurred authenticating: ' + error);
// we have hostGroups, templates and hosts here
}).then(function(r) {
// r[0] = hostGroups, r[1] = templates, r[2] = hosts
// check hostGroups
hostGroups = r[0].result;
if (!hostGroups.length) {
throw new Error('Error occurred retrieving host groups: ' + hostgroups.error.data);
}
// check templates
templates = r[1].result;
if (!templates.length) {
throw new Error('Error occurred retrieving templates: ' + template.error.data);
}
// check host information
hosts = r[2].result;
if (!hosts.length) {
throw new Error('Error occurred retrieving host groups: ' + hosts.error.data);
}
// utility function for retrieving a specific property from each array of objects
function getIds(array, prop) {
return array.map(function(item) {
return item[prop];
});
}
var groupIds = getIds(hostGroups, "groupid");
var hostIds = getIds(hosts, "hostid");
var templateIds = getIds(templates, "templateid");
return zabbixApi(methods.configExport(hostIds, templateIds, groupIds, SESSIONID));
}).then(function(config) {
if (config.error) {
throw new Error('Error occurred retrieving configuration: ' + config.error.message + ': ' + config.error.data);
}
// simultaneously write to file and logout (since these are not dependent upon one another)
return Promise.all(logout(), writeToFile(config));
}).then(function() {
// success here, everything done
}, function(err) {
// upon error, try to logout and rethrow earlier error
return logout().then(function() {
throw err;
}, function() {
throw err;
});
}).then(null, function(err) {
// error here
console.log(err);
});
}
function writeToFile (config) {
return new Promise(function(resolve, reject) {
fs.writeFile('zabbix_config_export.json', JSON.stringify(config), function (err) {
if (err) {
return Promise.reject(new Error('Error writing configuration to file: ' + err));
}
resolve();
});
});
}
main();
This also makes some other important structural changes/fixes:
Any time you have a reject/catch handler, if you don't either return a rejected promise or throw from that handler, then the promise chain will continue which is not what you want and you were doing that in almost every reject handler you had.
No need to have a reject handler for every single promise. If you just intend for any reject to stop the chain anyway, then you can just make sure that the reject reason is descriptive and do all failure logging one place at the end of the chain.
This uses Promise.all() in two places where operations can run in parallel because they are not dependent upon one another, but the code logic wants to know when they are all done.
writeToFile() is changed to return a promise.
Create a logout() function that can be safely called in several error paths so all error and success paths at least attempt the logout (if login succeeded)
Swap the two conditions when checking apiVersion because you should check !apiVersion first.
Promises.all is intended to handle asynchronous functions when they run in parallel and you need to wait until they all are finished. The promises in the code you shared are not independent so you can't use Promises.all here. Check out this reference to see the Promises.all usage.
Related
I know this topic as already asked many times before but I didn't find the right answer to do what I want.
Actually, I try to save two different list of JSON object in MongoDB via Mongoose. To perform both at the same time I use 'async'.
However, when I save it with the command insertMany() I get an error because he calls the callback of async before finishing the insertMany(). Therefore answer[0] is not defined.
What will be the proper way of doing it ?
Here is my code with the async:
const mongoose = require("mongoose");
const async = require("async");
const utils = require("../utils");
const experimentCreate = function(req, res) {
let resData = {};
let experimentList = req.body.experiment;
let datasetList = req.body.datasetList;
async.parallel(
{
dataset: function(callback) {
setTimeout(function() {
answer = utils.createDataset(datasetList);
callback(answer[0], answer[1]);
}, 100);
},
experiment: function(callback) {
setTimeout(function() {
answer = utils.createExp(experimentList);
callback(answer[0], answer[1]);
}, 100);
}
},
function(err, result) {
if (err) {
console.log("Error dataset or metadata creation: " + err);
sendJSONresponse(res, 404, err);
} else {
console.log("Experiment created.");
resData.push(result.dataset);
resData.push(result.experiment);
console.log(resData);
sendJSONresponse(res, 200, resData);
}
}
);
};
Then the two functions called createExp and createDataset are the same in another file. Like this:
const createDataset = function(list) {
let datasetList = [];
for (item of list) {
let temp = {
_id: mongoose.Types.ObjectId(),
name: item.name,
description: item.description,
type: item.type,
};
datasetList.push(temp);
}
Dataset.insertMany(datasetList, (err, ds) => {
if (err) {
console.log("Error dataset creation: " + err);
return [err, null];
} else {
console.log("All dataset created.");
return [null, ds];
}
});
};
There's a few problems with your code. For one, you're not returning anything in your createDataset function. You're returning a value in the callback of insertMany but it doesn't return that value to the caller of createDataset as it's within another scope. To solve this issue, you can wrap your Dataset.insertMany in a promise, and resolve or reject depending on the result of Data.insertMany like this:
const createDataset = function(list) {
let datasetList = [];
for (item of list) {
let temp = {
_id: mongoose.Types.ObjectId(),
name: item.name,
description: item.description,
type: item.type,
};
datasetList.push(temp);
}
return new Promise((resolve, reject) => {
Dataset.insertMany(datasetList, (err, ds) => {
if (err) {
console.log("Error dataset creation: " + err);
reject(err);
} else {
console.log("All dataset created.");
resolve(ds);
}
});
});
};
Now your return object is no longer going to be an array so you won't be able to access both the error and the result via answer[0] and answer[1]. You're going to need to chain a then call after you call createDataset and use callback(null, answer) in the then call (as that means createDataset executed successfully) or use callback(err) if createDataset throws an error like below:
dataset: function(callback) {
setTimeout(function() {
utils.createDataset(datasetList).then(answer => {
callback(null, answer);
}).catch(err => callback(err)); // handle error here);
}, 100);
}
Note: You'll most likely need to alter your createExp code to be structurally similar to what I've produced above if it's also utilizing asynchronous functions.
I am new to NodeJS and JavaScript. I am badly stuck in a problem:
I want to generate QR image of 'some text' and after generating it, I want to query my MySQL database and insert the image to database.
Problem is that QRCode.toDataURL of SOLDAIR module goes in running state and query is called before the QR image is returned from the .toDataUrl function.
Hence it results in error.
I tried everything, promises, nested promises, counters, if statements etc., but I am unable to find a solution.
My code:
router.post('/generateTicket', (req,res) => {
const query1 = `SELECT * FROM booking ORDER BY bookingID DESC LIMIT 1`;
const query2 = `INSERT INTO ticket (ticket_image,BookingID) SET ?`;
let bookingID;
let count;
let ticket_data = {};
Promise.using(mysql.getSqlConn(), conn => {
conn.query(query1).then(resultOfQuery1 => {
bookingID = resultOfQuery1[0].BookingID;
count = resultOfQuery1[0].PeopleCount;
console.log("ID = " + bookingID + " people count = "+count);
promiseToCreateQRcode().then(function (URLfromResolve) {
console.log("the url is " + URLfromResolve );
}).catch(function (fromReject) {
console.log("Rejected "+ fromReject);
}); // catch of promise to create QR
}).catch(err => {
res.json({ status: 500, message: 'Error Occured in query 1 ' + err });
}); // catch of query 1
});
});
var opts = {
errorCorrectionLevel: 'H',
type: 'image/png',
rendererOpts: {
quality: 0.3
}
};
let promiseToCreateQRcode = function () {
let QRImage;
return new Promise(function (resolve,reject) {
QRCode.toDataURL('text', function (err, url) {
if (err) throw err
console.log("\n"+url+"\n");
QRImage = url;
});
if (QRImage)
resolve(QRImage);
else
reject("error occured in url");
});
};
As u can see, the program jumps to if statement and the QR image is not generated yet, hence it goes in "reject":
Try this,
let promiseToCreateQRcode = function () {
return new Promise(function (resolve,reject) {
QRCode.toDataURL('text', function (err, url) {
if (err){
reject(err); // or some message
} else {
resolve(url);
}
});
});
};
This way promise will be resolved only when toDataURL returns QR image.
Have a look at How do I convert an existing callback API to promises?. You need to call resolve or reject in the asynchronous callback!
function promiseToCreateQRcode() {
return new Promise(function(resolve,reject) {
QRCode.toDataURL('text', function (err, url) {
if (err) {
reject(err);
} else {
console.log("\n"+url+"\n");
resolve(url);
}
});
});
}
Using this extra QRImage variable like you did cannot work.
I want to convert the following code to use promise. It is working and output a user's attributes within the active directory.
var client = ldap.createClient({
url: ldap_url
});
client.bind(ldap_username, ldap_password, function (err) {
client.search(ldap_dn_search, opts, function (err, search) {
search.on('searchEntry', function (entry) {
var user = entry.object;
// It is working!!!. It outputs all user attributes.
console.log(user);
});
});
});
The following is my attempt, butit doesn't output anything.
var Promise = require('promise');
var client_bind = Promise.denodeify(client.bind);
var client_search = Promise.denodeify(client.search);
client_bind(ldap_username, ldap_password)
.then(function(err){
client_search(ldap_dn_search, opts)
.then(function(search){
var search_on = Promise.denodeify(search.on);
search_on('searchEntry')
.then(function(entry){
var user = entry.object;
// It doesn't output anything !!!
console.log(user);
});
});
});
I had the same problem.
Search emits events, so we need something that processes them and passes further along the chain.
Here is piece of code, that works for me:
var ldap = require('ldapjs');
var promise = require('bluebird');
var client = ldap.createClient({url: app.settings['ldap']['server']});
var uid;
promise.promisifyAll(client);
function searchPromise(res, notfoundtext) {
return new Promise(function(resolve, reject) {
var found = false;
res.on('searchEntry', function(entry) {
found = true;
resolve(entry);
});
res.on('error', function(e) {
reject(e.message);
});
res.on('end', function() {
if (!found) {
reject(notfoundtext);
}
});
});
}
client.searchAsync(app.settings['ldap']['baseDn'], {filter: '(mail='+credentials.email+')', scope: 'sub'})
.then(function(res) {
return searchPromise(res, 'User isn\'t exists.');
})
.then(function (entry) {
uid = entry.object.uid;
return client.bindAsync(entry.object.dn, credentials.password);
})
.then(function() {
return client.searchAsync('cn='+app.settings['ldap']['group']+',cn=groups,'+app.settings['ldap']['baseDn'], {scope: 'sub', filter: '(memberUid='+uid+')'});
})
.then(function(res) {
return searchPromise(res, 'User is not in group ' + app.settings['ldap']['group']);
})
.then(function() {
console.log('All is ok');
})
.catch(function(message) {
console.log('Error:' + message);
});
Immediately after the search I add one more step that catches the events, processes them, and passes it further along the chain. This makes the function searchPromise.
Good luck coding )
Most likely those methods do require to be called on client as a context, so you will need to bind() them before passing them to Promise.denodeify:
var client_bind = Promise.denodeify(client.bind.bind(client));
var client_search = Promise.denodeify(client.search.bind(client));
Also, a proper use of promises would look like this:
client_bind(ldap_username, ldap_password).then(function() {
return client_search(ldap_dn_search, opts);
// ^^^^^^ always return something from the callback
}).then(function(search) { // flatten your chain
return Promise.denodeify(search.on).call(search, 'searchEntry');
// ^^^^^^ an alternative to `bind`
}).then(function(entry){
var user = entry.object;
console.log(user);
}).catch(function(err) { // always catch errors!
console.error(err);
});
Using Bluebird Promises, the easy way to do this is to create your client normally, and then run the promisifyAll() on the client.
var ldap = require('ldapjs');
var Promise = require('bluebird');
var client = ldap.createClient({
url: 'ldap://my-server:1234',
});
Promise.promisifyAll(client);
Now you can call client.addAsync() and client.searchAsync() and such.
client.bindAsync(secUserDn, secUserPassword)
.then(doSearch) // if it works, call doSearch
.catch(function (err) { // if bind fails, handle it
console.error('Error on bind', err)
});
function doSearch(data) {
client.searchAsync('CN=A Test,OU=Users,DC=website,DC=com', options)
.then(function (data) { // Handle the search result processing
console.log('I got a result');
})
.catch(function (err) { // Catch potential errors and handle them
console.error('Error on search', err);
});
}
i had the same issue here but i solved it by adding promise and resolve the response without using bluebird, this is an exemple of my code :
async getLdapUser(username: any): Promise<any> {
let myPromise = new Promise<boolean>((resolve, reject) => {
console.log('ssssssssss', username);
const adSuffix = 'OU=xxxx,OU=xxxxx,DC=xxxxxxx,DC=xxxxxx';
const password = 'xxxxxxxxxxxxx';
// Create client and bind to AD
const client = ldap.createClient({
url: 'ldap://1.1.1.1:389',
});
// promise.promisifyAll(client);
let resp = false;
// console.log(client);
client.bind('userTest', password,(err: any) => {
console.log('RESP', resp);
if (err) {
console.log('Error in new connetion ' + err);
} else {
/*if connection is success then go for any operation*/
console.log('Success');
const searchOptions: {} = {
scope: 'sub',
filter: '(sAMAccountName=' + username + ')',
attributes: ['sAMAccountName'],
};
client.search(adSuffix, searchOptions, (err: any, res: any) => {
assert.ifError(err);
res.on('searchEntry', (entry: any) => {
resp = true;
});
res.on('error', (error: any) => {
console.log('err');
reject(error.message);
});
await res.on('end', (result: any) => {
resolve(resp);
});
});
}
});
});
return myPromise;
}
I am looking to do a get, run a function on the results which will do some manipulation by updating a field, and then put that doc back into the database. Really my issue is being able to chain together multiple DB calls. I have been struggling with this the past week or so. Any suggestions appreciated, thanks.
Here is what I have tried so far but I am receiving an error:
function geocode_cleanup(request, response, next) {
r.table('dealer_locations').filter(r.row('geodata').match('No geodata found.'))
.do(function(row) {
var geodata = opencage_geocoder.geocode(row.Address, function(error, response) {
if (error) {
console.log("Error.");
row.geodata = "No geodata found.";
row.active = true;
} else if (response.length == 0) {
console.log("Empty response.");
} else {
console.log("Success.");
console.log(response);
var latitude = response[0].latitude;
var longitude = response[0].longitude;
row.geodata = r.point(longitude, latitude);
row.active = true;
}
});
return r.table('dealer_locations').update({
geodata: geodata
})
}).run(conn, function(error, cursor) {
response.setHeader("Content-Type", "application/json");
if (error) {
handleError(response, error);
} else {
cursor.toArray(function(error, results) {
if (error) {
handleError(response, error);
} else {
response.send(results);
};
});
}
next();
})
};
Also, this gives the desired results returned in the response, but the second db action never happens because I am still inside of the same db connection I think:
function geocode_cleanup(request, response, next) {
var conn = request._rdbConn;
r.table('dealer_locations').filter({geodata: "No geodata found."}).run(conn, function(error, cursor) {
if (error) {
handleError(response, error);
} else {
cursor.toArray(function(error, results) {
if (error) {
handleError(response, error);
} else {
var i = 1;
async.forEach(results, function(item, callback) {
var address = (item.Address + " " + item.City).toString();
opencage_geocoder.geocode(address, function(err, res) {
if (err) {
console.log(i);
console.log("Error.");
item.id = i;
item.geodata = "No geodata found.";
item.active = true;
i++;
callback();
} else if (res.length == 0) {
console.log(i);
console.log("Empty response.");
i++;
callback();
} else {
console.log(i);
console.log("Success.");
console.log(res);
var latitude = res[0].latitude;
console.log(i + " " + latitude);
var longitude = res[0].longitude;
console.log(i + " " + longitude);
item.id = i;
item.geodata = r.point(longitude, latitude);
item.active = true;
i++;
callback();
}
});
}, function() {
r.table('dealer_locations').insert(results, {
conflict: "replace"
}).run(request._rdbConn, function(error, results) {
if (error) {
console.log("Data not inserted!");
} else {
console.log("Data inserted!");
}
});
console.log("Done!");
response.send(results);
});
}
})
}
})
}
Here's a possible solution which uses promises to organize the code a little bit.
// Guarantee support for promises and provide the `promisify` function
var Promise = require('bluebird');
// Promisify the geocode function to make it easier to use
var geocode = Promise.promisify(opencage_geocoder.geocode);
function geocode_cleanup(request, response, next) {
var conn = request._rdbConn;
r
.table('dealer_locations')
.filter(r.row('geodata').match('No geodata found.'))
.coerceTo('array')
.run(conn)
.then(function(rows) {
// This promise will be resolve when all rows have been geocoded and updated
// We map the rows into an array of promises, which is what Promise.all takes
return Promise.all(rows.map(function (row) {
return geocode(row.Address)
.then(function (response) {
console.log("Success.");
var latitude = response[0].latitude;
var longitude = response[0].longitude;
row.geodata = r.point(longitude, latitude);
row.active = true;
// Return the row
return row;
});
});
}));
})
.then(function (rows) {
// Now that all `dealer_locations` have been updated, re-query them
return r
.table('dealer_locations')
.insert(rows, {conflict: "update", return_changes: true})
.run(conn);
})
.then(function (results) {
// Send the response;
response.setHeader("Content-Type", "application/json");
response.send(results);
return;
})
.catch(function (err) {
return handleError(null, error);
})
};
Some problems I noticed with your code:
1. Use of do
r.table('dealer_locations').filter(r.row('geodata').match('No geodata found.'))
.do(function(row) {
var geodata = opencage_geocoder.geocode ...
})
In this code snippet, you use a JS function inside of do. You can't do that. Remember that what happens inside of do happens in the RethinkDB server (not in your Node.js server). Your RethinkDB server has no knowledge of your opencage_geocoder function and so this woudn't work.
Whatever do returns must be a valid ReQL query or ReQL expression. You can't execute arbitrary JavaScript inside of it.
If you want to run JavaScript with your query results, you have to .run the query and then do whatever you want to do inside the callback or .then function. At that point, that code will get executed in JavaScript and not in your RethinkDB server.
2. Use of update
return r.table('dealer_locations').update({
geodata: geodata
})
The update method can only update a single document. You can't pass it an array of documents. In this scenario you what have needed to do r.table().get().update() in order for this to work, because you have to be referencing a single document when you update something.
If you have an array of documents that you want to update, you can use the forEach method.
r.table('hello')
.merge({
'new_property': 'hello!'
})
.forEach(function (row) {
// Insert that property into the document
return r.table('hello').get(row.id).update(row);
})
You can also do this (which you are already doing):
r.table('hello')
.merge({
'new_property': 'hello!'
})
.do(function (rows) {
// Insert that property into the document
return r.table('hello')
.insert(rows, {conflict: "update", return_changes: true});
})
OK, I have a suggestion. This queries for the documents you're interested in, modifies them (on your app server, not in the db) and then reinserts them using the nifty conflict: 'update' option. It also uses promises because I think that's a bit cleaner.
function geocode_cleanup(request, response, next) {
r.table('dealer_locations')
.filter(r.row('geodata').match('No geodata found.'))
.run(conn).then(function(cursor) {
var to_update = [];
return cursor.toArray().then(function getGeocodes(rows) {
return rows.map(function getGeocode(row) {
row.geodata = opencage_geocoder.geocode(row.Address, function(error, response) {
if (error) {
console.log("Error.");
row.geodata = "No geodata found.";
row.active = true;
} else if (response.length == 0) {
console.log("Empty response.");
} else {
console.log("Success.");
console.log(response);
var latitude = response[0].latitude;
var longitude = response[0].longitude;
row.geodata = r.point(longitude, latitude);
row.active = true;
}
});
return row;
});
});
}).then(function doneGeocoding(modified_rows){
return r.table('dealer_locations')
.insert(modified_rows, {conflict: "update", return_changes: true})('changes')
.coerceTo('array')
.run(conn);
}).then(function finishResponse(changes){
response.setHeader("Content-Type", "application/json");
response.send(results);
next();
}).catch(function(err) {
// handle errors here
});
};
Caveat emptor, I haven't run this, so there may be syntax errors and things
Can you please tell me how I can invoke a function when I make a meteor method/call.
For test purposes and keeping it simple, I get a value of 'undefined' on the client console.
Server.js
Meteor.methods({
testingFunction: function() {
test()
}
});
function test(){
var name = 'test complete'
return name
}
client.js
Template.profile.events({
'click #button': function (event) {
event.preventDefault();
Meteor.call('testingFunction', function(error, response) {
if (error) {
console.log(error);
} else {
console.log(response);
}
});
}
});
Any function without a return statement will return undefined. In this case, you need to add return test() to return the value of the call to test from your method.
Meteor.methods({
testingFunction: function() {
return test();
}
});
Here is a great example:
Client Side:
// this could be called from any where on the client side
Meteor.call('myServerMethod', myVar, function (error, result) {
console.log("myServerMethod callback...");
console.log("error: ", error);
console.log("result: ", result);
if(error){
alert(error);
}
if(result){
// do something
}
});
Server Side:
// use Futures for threaded callbacks
Future = Npm.require('fibers/future');
Meteor.methods({
myServerMethod: function(myVar){
console.log("myServerMethod called...");
console.log("myVar: " + myVar);
// new future
var future = new Future();
// this example calls a remote API and returns
// the response using the Future created above
var url = process.env.SERVICE_URL + "/some_path";
console.log("url: " + url);
HTTP.get(url, {//other params as a hash},
function (error, result) {
// console.log("error: ", error);
// console.log("result: ", result);
if (!error) {
future.return(result);
} else {
future.return(error);
}
}
);
return future.wait();
}//,
// other server methods
});