Is there a less-nested way to achieve the following with request-promise:
r = require('request-promise');
r(url1).then(function(resp1) {
// Process resp 1
r(url2 + 'some data from resp1').then(function(resp2) {
// Process resp 2
// .....
});
});
Each request is dependent on the result of the last, and so they need to be sequential. However, some of my logic requires up to five sequential requests and it causes quite the nested nightmare.
Am I going about this wrong?
You can return a Promise in the onFulfilled function provided to Promise.then:
r = require('request-promise');
r(url1).then(function(resp1) {
// Process resp 1
return r(url2 + 'some data from resp1');
}).then(function(resp2) {
// resp2 is the resolved value from your second/inner promise
// Process resp 2
// .....
});
This lets you handle multiple calls without ending up in a nested nightmare ;-)
Additionally, this makes error handling a lot easier, if you don't care which exact Promise failed:
r = require('request-promise');
r(url1).then(function(resp1) {
// Process resp 1
return r(url2 + 'some data from resp1');
}).then(function(resp2) {
// resp2 is the resolved value from your second/inner promise
// Process resp 2
// ...
return r(urlN + 'some data from resp2');
}).then(function(respN) {
// do something with final response
// ...
}).catch(function(err) {
// handle error from any unresolved promise in the above chain
// ...
});
Related
i have an array of variable number of urls and i must merge the data get with axion
the problem is then every axios call is relative to the data of the previus
if i have a fixed number of ulrs i can nest axion calls and live with that
i think to use something like this
var urls = ["xx", "xx", "xx"];
mergeData(urls);
function mergeData(myarray, myid = 0, mydata = "none") {
var myurl = "";
if (Array.isArray(mydata)) {
myurl = myarray[myid];
// do my stuff with data and modify the url
} else {
myurl = myarray[myid];
}
axios.get(myurl)
.then(response => {
// do my stuff and get the data i need and put on an array
if (myarray.length < myid) {
mergeData(myarray, myid + 1, data);
} else {
// show result on ui
}
})
.catch(error => {
console.log(error);
});
}
but i dont like it
there is another solution?
(be kind, i'm still learning ^^)
just to be clear
i need to optain
http request to "first url", parse the the json, save some data(some needed for the output)
another http request to "second url" with one or more parameter from previous data, parse the the json, save some data(some needed for the output)
... and so on, for 5 to 10 times
If your goal is to make subsequent HTTP calls based on information you get from previous calls, I'd utilize async/await and for...of to accomplish this instead of relying on a recursive solution.
async function mergeData(urls) {
const data = [];
for (const url of urls) {
const result = await axios.get(url).then(res => res.data);
console.log(`[${result.id}] ${result.title}`);
// here, do whatever you want to do with
// `result` to make your next call...
// for now, I am just going to append each
// item to `data` and return it at the end
data.push(result);
}
return data;
}
const items = [
"https://jsonplaceholder.typicode.com/posts/1",
"https://jsonplaceholder.typicode.com/posts/2",
"https://jsonplaceholder.typicode.com/posts/3"
];
console.log("fetching...")
mergeData(items)
.then(function(result) {
console.log("done!")
console.log("final result", result);
})
.catch(function(error) {
console.error(error);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.2/axios.min.js"></script>
Using async/await allows you to utilize for...of which will wait for each call to resolve or reject before moving onto the next one.
To learn more about async/await and for...of, have a look here:
developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of
Hope this helps.
I'm trying to retrieve all the child then when there's match display.
I print the value in the console and my code work well there after few second, but when I print it in the agent as a message it show not available before the response because it does not wait.
Here is my code:
function retrieveContact(agent) {
var query = admin.database().ref("/contacts").orderByKey();
query.once("value")
.then(function(snapshot) {
snapshot.forEach(function(childSnapshot) {
var key = childSnapshot.key;
var childName = childSnapshot.child('name').val();
if (agent.parameters.name == childName) {
console.log('find ' + childName);
agent.add('The email address for ' + childName + ' is ' + childSnapshot.child('email').val());
}
// console.log('testMode'+childName);
}); //// .then
}); //// .once }
SO, how can I wait my response then let the agent show the result?
How can I include the promise concept in my code ?
You don't show your entire Handler function, but if you're doing async operations (such as reading from the firebase db) you must return the Promise. This is how the Handler Dispatcher knows to wait for the Promise to complete before returning a response to the user.
In your case, it is probably as simple as
return query.once("value")
// etc
I am adding user validation an data modification page on a node.js application.
In a synchronous universe, in a single function I would:
Lookup the original record in the database
Lookup the user in LDAP to see if they are the owner or admin
Do the logic and write the record.
In an asynchronous universe that won't work. To solve it I've built a series of hand-off functions:
router.post('/writeRecord', jsonParser, function(req, res) {
post = req.post;
var smdb = new AWS.DynamoDB.DocumentClient();
var params = { ... }
smdb.query(params, function(err,data){
if( err == null ) writeRecordStep2(post,data);
}
});
function writeRecord2( ru, post, data ){
var conn = new LDAP();
conn.search(
'ou=groups,o=amazon.com',
{ ... },
function(err,resp){
if( err == null ){
writeRecordStep3( ru, post, data, ldap1 )
}
}
}
function writeRecord3( ru, post, data ){
var conn = new LDAP();
conn.search(
'ou=groups,o=amazon.com',
{ ... },
function(err,resp){
if( err == null ){
writeRecordStep4( ru, post, data, ldap1, ldap2 )
}
}
}
function writeRecordStep4( ru, post, data, ldap1, ldap2 ){
// Do stuff with collected data
}
Additionally, because the LDAP and Dynamo logic are in their own source documents, these functions are scattered tragically around the code.
This strikes me as inefficient, as well as inelegant. I'm eager to find a more natural asynchronous pattern to achieve the same result.
Any promise library should sort your issue out. My preferred choice is bluebird. In summary they help you in performing blocking operations.
If you haven't heard about bluebird then just use it. It converts all function of a module and return promise which is then-able. Simply put, it promisifies all functions.
Here is the mechanism:
Module1.someFunction() \\do your job and finally pass the return object to next call
.then() \\Use that object which is return from the first call, do your job and return the updated value
.then() \\same goes on
.catch() \\do your job when any error occurs.
Hope you understand. Here is an example:
var readFile = Promise.promisify(require("fs").readFile);
readFile("myfile.js",
"utf8").then(function(contents) {
return eval(contents);
}).then(function(result) {
console.log("The result of evaluating
myfile.js", result);
}).catch(SyntaxError, function(e) {
console.log("File had syntax error", e);
//Catch any other error
}).catch(function(e) {
console.log("Error reading file", e);
});
I could not tell from your pseudo-code exactly which async operations depend upon results from with other ones and knowing that is key to the most efficient way to code a series of asynchronous operations. If two operations do not depend upon one another, they can run in parallel which generally gets to an end result faster. I also can't tell exactly what data needs to be passed on to later parts of the async requests (too much pseudo-code and not enough real code to show us what you're really attempting to do).
So, without that level of detail, I'll show you two ways to approach this. The first runs each operation sequentially. Run the first async operation, when it's done, run the next one and accumulates all the results into an object that is passed along to the next link in the chain. This is general purpose since all async operations have access to all the prior results.
This makes use of promises built into the AWS.DynamboDB interface and makes our own promise for conn.search() (though if I knew more about that interface, it may already have a promise interface).
Here's the sequential version:
// promisify the search method
const util = require('util');
LDAP.prototype.searchAsync = util.promisify(LDAP.prototype.search);
// utility function that does a search and adds the result to the object passed in
// returns a promise that resolves to the object
function ldapSearch(data, key) {
var conn = new LDAP();
return conn.searchAsync('ou=groups,o=amazon.com', { ... }).then(results => {
// put our results onto the passed in object
data[key] = results;
// resolve with the original object (so we can collect data here in a promise chain)
return data;
});
}
router.post('/writeRecord', jsonParser, function(req, res) {
let post = req.post;
let smdb = new AWS.DynamoDB.DocumentClient();
let params = { ... }
// The latest AWS interface gets a promise with the .promise() method
smdb.query(params).promise().then(dbresult => {
return ldapSearch({post, dbresult}, "ldap1");
}).then(result => {
// result.dbresult
// result.ldap1
return ldapSearch(result, "ldap2")
}).then(result => {
// result.dbresult
// result.ldap1
// result.ldap2
// doSomething with all the collected data here
}).catch(err => {
console.log(err);
res.status(500).send("Internal Error");
});
});
And, here's a parallel version that runs all three async operations at once and then waits for all three of the to be done and then has all the results at once:
// if the three async operations you show can be done in parallel
// first promisify things
const util = require('util');
LDAP.prototype.searchAsync = util.promisify(LDAP.prototype.search);
function ldapSearch(params) {
var conn = new LDAP();
return conn.searchAsync('ou=groups,o=amazon.com', { ... });
}
router.post('/writeRecord', jsonParser, function(req, res) {
let post = req.post;
let smdb = new AWS.DynamoDB.DocumentClient();
let params = { ... }
Promise.all([
ldapSearch(...),
ldapSearch(...),
smdb.query(params).promise()
]).then(([ldap1Result, ldap2Result, queryResult]) => {
// process ldap1Result, ldap2Result and queryResult here
}).catch(err => {
console.log(err);
res.status(500).send("Internal Error");
});
});
Keep in mind that due to the pseudo-code nature of the code in your question, this is also pseudo-code where implementation details (exactly what parameters you're searching for, what response you're sending, etc...) have to be filled in. This should be illustrative of promise chaining to serialize operations and the use of Promise.all() for parallelizing operations and promisifying a method that didn't have promises built in.
I'm trying to use Q.allSettled in NodeJS (LoopbackJS) for the following scenario.
As an input to my method (REST API) I get an array of objects. Each of these objects internally has 2 objects: ObjA & ObjB.
For each item in the array:
If ObjA exists in the database get its ID and send mail #1
If it doesn't exist then insert ObjA and then get its ID and send mail #2.
Set ObjA.ID in ObjB and save ObjB.
Once all the objects are saved and emails are sent, then send the response of REST API call. If any of the previous tasks have failed, add the error details in the response.
Here is the pseudo code:
myModel.myMethod = function(input, cb){
var defResp = Q.defer();
var promises = [];
try {
var defObjAList = Q.defer();
promises.push(defObjAList.promise);
getObjAIfItExists(input).done(function(inputWIds) { // input[] with IDs populated
inputWIds.forEach(function(item){
var defObjA = Q.defer();
if(item.objA.id){ // objA already exists in DB
var options = { ... }; // options for sending mail #1
promises.push(Q.ninvoke(Email, "send", options)); // using loopback Email which internally uses nodemailer
defObjA.resolve(item.objA.id);
} else {
Q.ninvoke(ObjA, "save", item.objA).done(function (savedA) {
var options = { ... }; // options for sending mail #2
promises.push(Q.ninvoke(Email, "send", options)); // using loopback Email which internally uses nodemailer
console.log(promises.length); // prints 3
defObjA.resolve(savedA.id);
}, function(err){
defResp.reject(err);
});
}
var defObjB = Q.defer();
promises.push(defObjB.promise);
defObjA.promise.done(function(objAId){
item.objB.objAId = objAId;
promises.push(Q.ninvoke(ObjB, "save", item.objB));
console.log(promises.length); // prints 4
}, function(err){
defResp.reject(err);
});
console.log(promises.length); // prints 2
defObjAList.resolve("Process Complete");
}); // inputWIds.forEach
}, function(err){
defResp.reject(err);
}); //getObjAIfItExists
console.log(promises.length); // prints 1
Q.allSettled(promises).done(function (results) {
console.log(results.length); // prints 1
console.log(JSON.stringify(results)); // prints result[] with single item
var response = {};
response.errors = [];
// iterate on results and check if any promise was failed, if yes add the reason to errors array
defResp.resolve(response);
});
} catch (err) {
defResp.reject(err);
}
return defResp.promise.nodeify(cb);
}
For the testing purpose my input array only contains one item. So the total number of promises that get added to promises[] are 4. But in spite of that the result array contains only 1 item.
My code is working for normal case, but if there is an error e.g. while sending mail, I need to send it in response. That is not working because the results array doesn't contain the email sending promise's output.
Can someone tell me what am I doing wrong? And if I need to handle it in some other way?
I've found out a way to handle the scenario described above. But I'm still open for alternate approaches.
Instead of resolving defObjAList after adding 2nd promise to promises array, I'm waiting till all the promises are added to the array.
Then inside the allSettled handler (which was monitoring only 1 promise), I again call Q.allSettled by passing promises array again (which now contains 4 promises). Now this second handler for allSettled gets the results array with 4 items for all the promises that were added.
Alternatively I could simple wait for defObjAList promise to fulfill and then call Q.allSettled for rest of the promises. This will be better in terms of performance. But for the time being I've kept 2 calls to Q.allSettled.
:
:
inputWIds.forEach(function(item, ind){ // added ind param
// handle ObjA
var defObjB = Q.defer();
promises.push(defObjB.promise);
defObjA.promise.done(function(objAId){
item.objB.objAId = objAId;
promises.push(Q.ninvoke(ObjB, "save", item.objB));
console.log(promises.length); // prints 4
if(ind == inputWIds.length-1){ // check if its the last iteration
defObjAList.resolve("Process Complete");
}
}, function(err){
defResp.reject(err);
});
console.log(promises.length); // prints 2
// defObjAList.resolve("Process Complete"); - removed from here
}); // inputWIds.forEach
:
:
Q.allSettled(promises).done(function (resultsOld) {
Q.allSettled(promises).done(function (results) {
console.log(results.length); // prints 4
// handle results array and send response
});
});
Dears ,
How can i run promises in nodejs sequentially , in the following example am looping through array of hours then for each fetched hour get result from the database , the issue here : am getting results but i want it sequentially same order that i got hours .
angular.forEach(SharedVar.getCategories(), function (h) {
t = h.split('-', 2);
t = t[0];
RESTApi.getAnswerdCallsByHour(t).then(function (answerdTotal) {
$scope.answerdCallsTotalByHour.push(answerdTotal);
var d = SharedVar.getDataS();
d[count] = answerdTotal;
SharedVar.setDataS(d);
count++;
});
});
Thanks ,
var promise = Promise.resolve(); // make an empty promise in the way you do it with your promise library
angular.forEach(SharedVar.getCategories(), function (h) {
promise.then(function() {
return RESTApi.getAnswerdCallsByHour(t).then(function (answerdTotal) {});
});
});
The way to do it sequently would be to do one Request and do the next request inside the promise.
I think the better approach by far is to extend your SharedVar.setDataS(d) function in a way, that it does not depend on getting the data sequentially. Like having a SharedVar.setDataS(d, index) and using the config var in your $http.get (or whatever) functioncall inside your RESTApi to promote that index all the way to the promise.
If your RESTApi looks like this:
var RESTApi = {
getAnswerdCallsByHour : function(hour) {
var url = "bla.com/myservice?hour=" + hour;
return $http.get(url).data;
}
// Some other Methods...
Then you need a way to pass something to "reorder" your Data when it arrives asynchronously, this could be a index you count up or in your case maybe the hour Variable:
var RESTApi = {
getAnswerdCallsByHour : function(hour) {
var url = "bla.com/myservice?hour=" + hour;
var config = [];
config.hour = hour;
return $http.get(url, config); // Return the promise not just data or a specific field
}
// Some other Methods...
Now when your promise is fullfiled you can access your "hour" Variable like so:
var d = SharedVar.getDataS();
d[promise.config.hour] = promise.data;
SharedVar.setDataS(d);
Now you know what piece of data correlates to which request and you do not need to recieve Data in order. The last piece only works properly when hours runs sequential from 0 to 23, if that isn't the case you need to:
var RESTApi = {
getAnswerdCallsByHour : function(hour, index) {
var url = "bla.com/myservice?hour=" + hour;
var config = [];
config.index = index;
return $http.get(url, config);
}
// Some other Methods...
...
...
var d = SharedVar.getDataS();
d[promise.config.index] = promise.data;
SharedVar.setDataS(d);
Safari's answer is how I typically handle this. (Sorry, I don't have enough rep to comment yet...) You were experiencing problems with it because the example provided does not capture and use the new promise in subsequent loops. See my comments on the slightly modified version here:
var promise = Promise.resolve();
angular.forEach(SharedVar.getCategories(), function (h) {
t = h.split('-', 2);
t = t[0];
// You must capture the new promise here; the next loop will wait
// for the promise returned from getAnswerdCallsByHour to resolve.
promise = promise.then(function() {
// Halt downstream promises until this returned promises finishes
return RESTApi.getAnswerdCallsByHour(t).then(function (answerdTotal) {
$scope.answerdCallsTotalByHour.push(answerdTotal);
var d = SharedVar.getDataS();
d[count] = answerdTotal;
SharedVar.setDataS(d);
count++;
});
});
});