I would like to have a function called on the return of the last promise made during a forEach() loop.
Sample code:
var mainList = getArrayData()
mainList.forEach((item,i)=>{
mongooseSchema.find(_id:item.id).exec((err,doc)=>{
doStuff(doc)
})
})
Any code after the second block will run immediately after all the mongo queries are sent off. If you wrap the whole thing in a promise (i.e. the array data is from a separate mongo query) you still get the same effect.
Is there any way I can have a special function/callback for the last query, or even better, a promise that returns after all the queries have returned?
Thanks
You could use Promise.all. It will fire off once all of the promises in an array are resolved.
The Promise.all(iterable) method returns a promise that resolves when all of the promises in the iterable argument have resolved, or rejects with the reason of the first passed promise that rejects.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
var mainList = getArrayData()
let promises = [];
mainList.forEach((item,i)=>{
let p = new Promise((resolve, reject) => {
mongooseSchema.find(_id:item.id).exec((err,doc)=>{
doStuff(doc)
resolve();
});
});
promises.push(p);
});
Promise.all(promises).then(() => {
// do something when all are resolved
});
Related
This is my first time to write while loop with Promise and callback. I don't know why it leads to infinite loop. How to fix it?
async function getResult(){
return new Promise((resolve, reject) => {
let params ="some input and setting";
let next = "hasNext";
let array = [];
let error = null;
while(next !== null){
checkNext(params, function(err,data) { //checkNext is a function to return the current list and check wether there has the next list
if(err){
next = null;
error = err;
}else{
next = data.hasNext; // if there is not next list, data.hasNext = null
array = array.concat(data.array); // data.array return the current list
}
});
}
if(error !== null){
reject(error);
}else{
resolve(array); // I want to return all lists
}
});
}
It leads to an infinite loop because checkNext() is asynchronous and non-blocking so your while() just runs forever before even one call to checkNext() gets a chance to finish and call its callback.
You never use a while() loop waiting for some asynchronous things to finish in Javsacript (except with await as shown below) because with the event driven architecture of nodejs, the while loop never returns control back to the event loop so no asynchronous operation can never get its completion event processed and thus the thing you are waiting for never gets a chance to happen. Things are different (as shown below) if you use await to await a promise that is connected to your asynchronous event. Then, you can use a while() loop successfully.
With asynchronous operations where you're going to use promises, you pretty much always want to promisify your asynchronous operations so all your control flow is with promises, not plain callbacks as the two don't mix very well. This is what I would suggest:
const { promisify } = require('util');
const checkNextP = promisify(checkNext);
async function getResult() {
let params = "some input and setting";
let next = "hasNext";
let array = [];
while (next !== null) {
let data = await checkNextP(params);
next = data.hasNext; // if there is not next list, data.hasNext = null
array = array.concat(data.array); // data.array return the current list
}
return array;
}
Here, the while loop works because we're using await with a promise returned from checkNextP() and that await suspends the execution of the function until that promise resolves/rejects.
A little more explanation about how async functions work
At the point we hit the first await this async function will automatically return a promise. The caller will get that promise at that point. Then, when the promise with that first await resolves, the function will resume, you will get the first data value and the rest of your loop executes. This process will repeat until next is null. At that point, your while() loop will be done and the return array statement will execute. Because this is an async function, what that return statement really does is it resolves the promise that the async function previously returned and sets the array to be the resolved value of that promise.
If, the promise from checkNextP() rejects, then the await checkNextP() will throw the rejection and since we don't have a try/catch around it, the async function will automatically catch that throw and it will reject the promise that the async function has previously returned, causing the caller to get a promise rejection with whatever error checkNextP() rejected with. So, the error handling here works too.
The caller of getResult(), just needs to do something like this:
getResult().then(results => {
console.log(results);
}).catch(err => {
console.log(err);
});
Or, the caller could also be in an async function itself and use await and try/catch to catch errors.
i think both of them are giving me same promise but later one doesn't work as promise. it gives me "TypeError: data.then is not a function"
try{
await docClient.get(params).promise().then(x => console.log(x));//this one works
const data = await docClient.get(params).promise();
data.then(x =>console.log(x));//this.doesen't
}
catch(err){
console.log(err);
}
Function Logs:
START RequestId: 914e5709-7cf2-4978-9b58-d338ebee52dc Version: $LATEST
2019-11-08T04:39:22.259Z 914e5709-7cf2-4978-9b58-d338ebee52dc
INFO "Event: event"
2019-11-08T04:39:22.437Z 914e5709-7cf2-4978-9b58-d338ebee52dc
INFO { Item: { firstname: 'Bob', id: '12345', lastname: 'Johnson' } }
2019-11-08T04:39:22.487Z 914e5709-7cf2-4978-9b58-d338ebee52dc
INFO TypeError: data.then is not a function
at Runtime.exports.handler (/var/task/index2.js:21:11)
at process._tickCallback (internal/process/next_tick.js:68:7)
await docClient.get(params).promise().then(x => console.log(x));
Means that the entire expression docClient.get(params).promise().then(x => console.log(x)) returns a Promise and you await until it's resolved.
await docClient.get(params).promise()
Means you get a promise out of this part of the expression and await it thus unwrapping it into a plain value, which means that data is not a Promise any more and thus data.then(x =>console.log(x)) fails.
If you want to preserve the Promise in the second case, then you can not await it:
const data = docClient.get(params).promise();
await data.then(x =>console.log(x));
Although this is probably not very good, since you're better off using either await or Promises instead of breaking the chained Promise API to await part of it. You can just drop the Promises API altogether
const data = await docClient.get(params).promise();
console.log(data);
Worth noting that you can use use the alternative syntax with a callback but Promise or await are probably better:
const data = docClient.get(params, function(err, data) {
console.log(data);
});
In your first statement:
docClient.get(params).promise()
is a promise, so of course, you can add a .then() handler to it like:
docClient.get(params).promise().then(...)
Putting an await in front of it:
await docClient.get(params).promise().then(...)
doesn't affect the .then() itself, that's still a method call on docClient.get(params).promise(). It just awaits the result of the .then(). If you need some extra parens to see the order of evaluation, it would be like this:
await ( docClient.get(params).promise().then(...) )
though the extra parens are not needed in execution.
In your second statement:
const data = await docClient.get(params).promise();
Because of the await, the variable data contains the resolved value of the promise. It's not a promise. So, when you attempt:
data.then()
there's no .then() method on the value in the data variable so it's an error.
Like it says in the error, data.then is not a function. Since you awaited the promise the value is scalar at that point and you would need to console.log(data) to see it.
I am very new to node.js and here is my problem
I have a nested set of maps here(I have tried using for-each as well).
All the "get..." methods nested are returning a promise defined by me. I have to loop over the data returned by the methods continuously and use it to call the next "get.." method(there is a dependency). After completing the entire nested loops, the "allobj" array should have my final result and I am trying to return it once all the looping is done. I have not had any luck so far, as an empty array gets returned.
var allobj = new Array();
var catcount = 0;
//categories.forEach(function(category){
var p = categories.map(function(category,index,array){
catcount++;
return gettests(runid,category).then(function(tests){
//console.log(tests.sort().reverse()); //this is sync
//tests.forEach(function(test){
var t = tests.map(function(test,index,array){
return getmetriccategories(runid,category,test).then(function(data){
//console.log(data);
//data.forEach(function(mc){
var d = data.map(function(mc,index,array){
return getmetrics(runid,category,test,mc).then(function(data1){
var x = data1.map(function(metric,index,array){
allobj.push(metric);
return metric;
});
});
});
})
})
})
})
//return when all nested loops are done
res.send(allobj);
Your functions getmetriccategories and getmetrics return Promises, at least, their return values seem to have a then method which is an indicator for that. This means, they work asynchronously. So, the map calls don't return the results directly, but an array of Promises. To wait for all of these Promises to be fulfilled (which means, the asynchronous functions are completed), you can use Promise.all function:
Promise.all(data.map(...))
.then(function (result) {
res.send(result)
})
As you see, Promise.all returns a new Promise, so you can use then to receive the result and send it with res.send.
I need help with debugging this code/or learn efficient way to do it- I tried using bluebird.each to capture all executions within my forEach, but didn't get it work. Same with setting up a new promise with pure javascript. I need help how to execute my forEach FIRST and move on.
let arr = [];
let fields = ['environment', 'habitat', 'earth]
Promise.each(fields, field => {
nano.db.use(field).view('options', 'options')
.spread((body, header) => {
arr.push(body.rows);
})
}).then(() => console.log(arr))
Expected outcome:
arr to console.log ====> [1,2,3,4,5]
Actual outcome:
arr is an empty array ====> [ ]
I see it's a problem with asynchronicity, but I can't seem to figure out how to actually make this work. any input or resources will be greatly appreciated!
I haven't actually ran your code so sorry if I'm incorrect, but from looking at it and the bluebird docs I assume the correction you need to make is return your nano.db call wrapped in a promise inside the Promise.each
let arr = [];
let fields = ['environment', 'habitat', 'earth']
Promise.each(fields, field => {
return new Promise ((resolve, reject) => {
nano.db.use(field).view('options', 'options')
.spread((body, header) => {
arr.push(body.rows);
resolve();
})
});
}).then(() => console.log(arr))
I believe your assumption is right that you're having a problem with asynchronicity when getting an empty array back instead of what you expect. I'm assuming the .then method is firing before the nano.db gets back with the data.
I wrapped your call of nano.db in a promise so that it will await nano.db finishing since Promise.each supports returning promises inside it.
Bluebird's promise docs state with Promise.each.
If the iterator function returns a promise or a thenable, then the
result of the promise is awaited before continuing with next
iteration.
So, if a promise is not returned in your Promise.each and anything that is asynchronous happens inside then just as with a then or a catch method on a promise under the same circumstances.
As I do not know bluebird there may be a way to change that promise to be more bluebird like. The promise I wrapped around the nano.db call is just a normal es6 promise which bluebird may or may not have a different api for creating promises.
I am using Promise.all() within a promise chain. Each promise within the Promise.all() returns a string.
The issue im having is that Promise.all() returns a Promise object to the next promise and I would like to continue the promise chain for each string.
Heres an example:
....
return Promise.all(plugins);
})
.then(function(response) {
console.log(response)
....
The response looks like:
[ 'results from p1', 'results from p2' ]
Is there any way to continue the promise chain for each of the results rather than continuing with a single object containing all results?
Promise.all expects an array of promises. So plugins is an array of promises and all the more: plugin is a Promise.
So you can just chain your plugin Promise.
This would thus become
Promise.all(plugins.map(function(plugin){
return plugin.then(function(yourPluginString){
return 'example '+ yourPluginString;
})
}))
Promise.all(), by its design returns a single promise who's resolved value is an array of resolved values for all the promises you passed it. That's what it does. If that isn't what you want, then perhaps you are using the wrong tool. You can process the individual results in a number of ways:
First, you can just loop over the array of returned results and do whatever you want to with them for further processing.
Promise.all(plugins).then(function(results) {
return results.map(function(item) {
// can return either a value or another promise here
return ....
});
}).then(function(processedResults) {
// process final results array here
})
Second, you could attach a .then() handler to each individual promise BEFORE you pass it to Promise.all().
// return new array of promises that has done further processing
// before passing to Promise.all()
var array = plugins.map(function(p) {
return p.then(function(result) {
// do further processing on the individual result here
// return something (could even be another promise)
return xxx;
});
})
Promise.all(array).then(function(results) {
// process final results array here
});
Or, third if you don't really care when all the results are done and you just want to process each one individually, then don't use Promise.all() at all. Just attach a .then() handler to each individual promise and process each result as it happens.
You can use a tool like https://github.com/Raising/PromiseChain
and implement what you say as
//sc = internalScope
var sc = {};
new PromiseChain(sc)
.continueAll([plugin1,plugin2,plugin3],function(sc,plugin){
return plugin(); // I asume this return a promise
},"pluginsResults")
.continueAll(sc.pluginsResults,function(sc,pluginResult){
return handlePluginResults(pluginResult);
},"operationsResults")
.end();
I didnt test the code if you have any problem PM me