I am writing a Node route which should push objects onto an array declared outside the forEach loop after the objects have a property added to them. When I console.log the array within the loop, it seems to be taking on data, but when I return it to the client-side. It is empty.
var todaysTopItemsBySaleFrequency = [];
listOfItemIdsAndSaleFrequency.forEach((item) => {
Product.findById(item.itemId).then((foundItem) => {
var fullItemData = foundItem.toJSON();
fullItemData.occurrences = item.occurrences;
todaysTopItemsBySaleFrequency.push(fullItemData);
console.log(todaysTopItemsBySaleFrequency);
});
});
return res.status(200).json(todaysTopItemsBySaleFrequency);
The console.log statement shows that the array called todaysTopItemsBySaleFrequency is being populated correctly, but why is it empty when I return it to the client?
The callback function you're passing to Product.findById(item.itemId).then(...) is not invoked immediately. Your outer forEach completes and you return before any of your callbacks are invoked.
Mongoose's findById() method returns a promise. You can use Promise.all() to wait for an array of promises to complete, and then set res.status(200).json(...). Because this happens asynchronously you should also present an asynchronous interface, for example by returning a promise yourself.
Here's a version that gathers all of the responses and returns a promise that resolves with your original return value:
var todaysTopItemsBySaleFrequency = [];
return Promise.all(listOfItemIdsAndSaleFrequency.map((item) => {
return Product.findById(item.itemId).then((foundItem) => {
var fullItemData = foundItem.toJSON();
fullItemData.occurrences = item.occurrences;
todaysTopItemsBySaleFrequency.push(fullItemData);
console.log(todaysTopItemsBySaleFrequency);
});
})).then(() => res.status(200).json(todaysTopItemsBySaleFrequency));
Related
I'm getting some unusual behaviour.
Basically as part of my code I have a function which utilizes nested for loops to build a promise and add it to a list of promises.
After the nested loops are complete, I'd like to evaluate the promise list using promise.all().
I have successfully managed to do this with a single forEach loop in the past, the nesting seems to cause some issues, namely, testing reveals that the Promise.all is being called before the nested forEach loop terminates, resulting in it being called on an empty list and hence returning an empty list.
I have a feeling that the issue is that I'm missing a return statement somewhere in my nested forEach loop as mentioned in this answer but I have not been able to determine where.
culprit.js
const otherModule = require("blablabla")
const otherOtherModule = require("blablabla2")
function nestedFunction(list){
var promises = [];
list.forEach(element => {
otherModule.getSublist(element).then(sublist => {
sublist.forEach(subelement => {
promises.push(otherOtherModule.promiseResolvingFunction(subelement));
});
});
});
return Promise.all(promises);
}
module.exports = {
nestedFunction : nestedFunction
}
culprit.test.js
const culprit = require("culpritpath")
// for mocking
const otherModule = require("blablabla")
otherModule.getSublist = jest.fn(() => Promise.resolve([{}, {}, {}]))
const otherOtherModule = require("blablabla2")
otherOtherModule.promiseResolvingFunction = jest.fn(() => Promise.resolve())
describe("nestedFunction()", ()=>{
it("returns an array of resolved promises", () => {
return culprit.nestedFunction([{}, {}]).then(res => {
expect(res).toHaveLength(6);
})
})
})
instead I get that res is []. Further tests show that promiseResolvingFunction is being called the right number of times, so as I understand, Promise.all is being called before the nested forEach loop finishes.
PS: I am still getting started with promises and TDD, I am more than happy to hear feedback on any code smell.
Yeah so the problem I see is that your for each loop is calling asynchronous code and expecting it to execute synchronously.
I'd probably do something like...
var promises = list.map(element => {
return otherModule.getSublist(element).then(sublist => {
// Map all of the sublists into a promise
return Promise.all(sublist.map(subelement => {
return otherOtherModule.promiseResolvingFunction(subelement));
}));
});
});
return Promise.all(promises);
Of course then you'd end up with an array of arrays. If you wanted to keep the result a flat array of sublist items, another option would be to first fetch all of your lists, then fetch all of your sublists from those results...
return Promise.all(list.map( element => otherModule.getSublist(element)))
.then((sublists) => {
let subListPromises = [];
// Loop through each sublist, turn each item in it into a promise
sublists.forEach( sublist => {
sublistPromises = [
...sublistPromises,
sublist.map( subelement => otherOtherModule.promiseResolvingFunction(subelement))
]
})
// Return a promise dependent on *all* of the sublist elements
return Promise.all(sublistPromises)
})
You execute Promise.all before the array has been populated (which happens asynchronously).
It may look difficult to deal with nested promises, but just apply Promise.all to the inner arrays of promises, and then at the outer level, apply Promise.all to all those from the inner level.
Then you're not ready yet, as now you have a promise that resolves to an array of arrays (corresponding to the originally nested promises), so you need to flatten that with the quite new .flat method, or with [].concat:
function nestedFunction(list) {
// Get promise for the array of arrays of sub values
return Promise.all(list.map(element => {
return otherModule.getSublist(element).then(sublist => {
// Get promise for the array of sub values
return Promise.all(sublist.map(subelement => {
return otherOtherModule.promiseResolvingFunction(subelement);
}));
});
})).then(matrix => [].concat(...matrix)); // flatten the 2D array
}
You need to nest your promise resolution. Something like this:
const otherModule = require("blablabla")
const otherOtherModule = require("blablabla2")
function nestedFunction(list){
var promises =
list.map(element => {
return otherModule.getSublist(element).then(sublist => {
return Promise.all(
sublist.map(subelement => {
return otherOtherModule.promiseResolvingFunction(subelement);
})
);
});
});
return Promise.all(promises);
}
module.exports = {
nestedFunction : nestedFunction
}
var promiseReturningFuncs = [];
for(var i = 0; i < 5; i++){
promiseReturningFuncs.push(askQuestion);
}
var programmers = [];
Promise.reduce(promiseReturningFuncs, function(resp, x) {
console.log(typeof resp);
if(typeof resp != "function") {
programmers.push(resp);
}
return x();
})
.then(function(resp) {
programmers.push(resp);
console.log(programmers);
});
My goal: execute the askQuestion function in series and resolve an array of objects created by that function. (this function must execute in series so that it can respond to user input)
So imagine that the askQuestion function returns a promise that resolves a object I want to add to an array.
This is my messy way of doing it.
I am looking to find a cleaner way of doing it, ideally, i wouldn't even need to push to an array, I would just have a final .then, where the response is an array.
Since you appear to be using the Bluebird promise library, you have a number of built-in options for sequencing your promise returning functions. You can use Promise.reduce(), Promise.map() with a concurrency value of 1, Promise.mapSeries or Promise.each(). If the iterator function returns a promise, all of these will wait for the next iteration until that promise resolves. Which to use depends more upon the mechanics of how your data is structured and what result you want (neither of which you actually show or describe).
Let's suppose you have an array of promise returning functions and you want to call them one at a time, waiting for the one to resolve before calling the next one. If you want all the results, then I'd suggest Promise.mapSeries():
let arrayOfPromiseReturningFunctions = [...];
// call all the promise returning functions in the array, one at a time
// wait for one to resolve before calling the next
Promise.mapSeries(arrayOfPromiseReturningFunctions, function(fn) {
return fn();
}).then(function(results) {
// results is an array of resolved results from all the promises
}).catch(function(err) {
// process error here
});
Promise.reduce() could also be used, but it would accumulate a single result, passing it from one to the next and end with one final result (like Array.prototype.reduce() does).
Promise.map() is a more general version of Promise.mapSeries() that lets you control the concurrency number (the number of async operations in flight at the same time).
Promise.each() will also sequence your functions, but does not accumulate a result. It assumes you either don't have a result or you are accumulating the result out-of-band or via side effects. I tend to not like to use Promise.each() because I don't like side effect programming.
You could solve this in pure JS using ES6 (ES2015) features:
function processArray(arr, fn) {
return arr.reduce(
(p, v) => p.then((a) => fn(v).then(r => a.concat([r]))),
Promise.resolve([])
);
}
It applies the function given to the array in series and resolves to an array of the results.
Usage:
const numbers = [0, 4, 20, 100];
const multiplyBy3 = (x) => new Promise(res => res(x * 3));
// Prints [ 0, 12, 60, 300 ]
processArray(numbers, multiplyBy3).then(console.log);
You'll want to double check browser compatibility but this works on reasonably current Chrome (v59), NodeJS (v8.1.2) and probably most others.
You can use recursion so that you can move to the next iteration in a then block.
function promiseToExecuteAllInOrder(promiseReturningFunctions /* array of functions */) {
var resolvedValues = [];
return new Promise(function(resolve, reject) {
function executeNextFunction() {
var nextFunction = promiseReturningFunctions.pop();
if(nextFunction) {
nextFunction().then(function(result) {
resolvedValues.push(result);
executeNextFunction();
});
} else {
resolve(resolvedValues);
}
}
executeNextFunction();
}
}
Executing one after another using a recursive function( in a non promise way):
(function iterate(i,result,callback){
if( i>5 ) callback(result);askQuestion().then(res=>iterate(i+1,result.concat([res]),callback);
})(0,[],console.log);
For shure this can be wrapped in a promise:
function askFive(){
return new Promise(function(callback){
(function iterate(i,result){
if( i>5 ) callback(result);askQuestion().then(res=>iterate(i+1,result.concat([res]),callback);
})(0,[],console.log);
});
}
askFive().then(console.log);
Or:
function afteranother(i,promise){
return new Promise(function(resolve){
if(!i) return resolve([]);
afteranother(i-1,promise).then(val=>promise().then(val2=>resolve(val.concat([val2])));
});
}
afteranother(5,askQuestion).then(console.log);
This is my JS:
self.obj = {}
self.obj.accessErrors = function(data) {
var cerrorMessages = [];
for (prop in data) {
if (data.hasOwnProperty(prop)){
if (data[prop] != null && data[prop].constructor == Object) {
self.obj.fetch[accessErrors](data[prop]);
}
else {
cerrorMessages.push(data[prop]);
}
}
}
return cerrorMessages;
};
self.obj.fetch = {
X: function() {
// do stuff.
},
Y: function(callback) {
$http.get('/yposts/').then(
function(response) {
self.posts = response.data;
callback(self.posts);
},
function(response) {
self.posts = {};
self.obj.accessErrors(response.data).then(function(cerrrorMessages) {
callback(posts, cerrorMessages);
});
}
);
}
};
And I am getting an error pointing to this line:
self.obj.accessErrors(response.data).then(function(cerrrorMessages) {
The error says:
TypeError: self.obj.accessErrors(...).then is not a function
Any idea how to solve this?
self.obj.accessErrors(response.data) does not return a promise so therefore, you can't use promise methods on it.
If you want it to return a promise and you want that promise to reflect when all the fetch() operations are done and those operations are actually async, then you will have to make all your async code into using promises and you will have to combine them all using Promise.all() or the angular equivalent and convert from using callbacks in fetch to just using a promise. Right now, you have a mix which is difficult to program with.
The .then() construction is only needed when using Promise objects - essentially, instead of returning a value, the function returns an object that resolves to a value at some point in the future (which is then passed into the function that you pass to .then().
But you are right in that you need an asynchronous pattern to do this correctly, since fetch.Y is an asynchronous method. A good thing to do would be to create an array of promises at the beginning of your accessErrors function, like so:
var fetchPromises = [];
and then replace self.obj.fetch[accessErrors](data[prop]); with something that calls push on that array to add the promise that fetch returns to it.
Then, instead of returning accessErrors, return Promise.all(fetchPromises).
This will require some fairly significant modification to your code, however - namely, you will need to rewrite it so that it uses the Promise API instead of this callback by itself (which shouldn't be too difficult to do).
I am currently struggling with the control flow of promise (promise newbie!).
I make a call to Redis which returns an array object. I then iterate through each of the results and call back to Redis to get a value and wish to populate these in to a final object out.
The out object is never populated, im guessing as the forEach has not completed:
(note the Redis client lib returns a when.js based promise as default)
var out = {};
REDISCLIENT.keys("apikey:*")
.then(function (replies) {
replies.forEach(function (key, index) {
REDISCLIENT.get(key)
.then(function (value) {
out[key] = value;
})
.done(console.log(out));
});
})
.catch(console.log)
.done(console.log(out));
How can I guarantee that the forEach loop is complete?
I have read many similar posts (I know this is a duplicate) however have not been able to understand why the inner done() method does not contain the fully complete out obj.
Im guessing I need to wrap the forEach itself in a promise? Appreciate any direction.
Update 1: huge thanks to #David_Aurelio. I now need to populate out with the key and values. Here is my attempt:
GLOBAL.REDISCLIENT.keys("apikey:*")
.then(function (replies) {
return when.all(replies.map(function (key, index) {
return GLOBAL.REDISCLIENT.get(key)
.then(function (val) {
out[key] = val;
console.log(key, val);
});
}));
})
.catch(console.log)
.done(function (out) {console.log(out); });
The inner console.log prints the correct key/values
key1 val1
key2 val2
The final done now prints:
[ undefined, undefined ]
It's important to understand that flow control and the data conveyed by a promise chain, are determined by :
the composition of the chain, and any inner chain(s)
promise aggregators, such as when.all()
return statements
Here's how to achieve what you want with out as an inner member.
REDISCLIENT.keys("apikey:*")
.then(function (replies) {
var out = {}: //<<<<< initiate `out` as an inner member
return when.all(replies.map(function (key, index) { //<<<<< here's David Aurelio's when.all(replies.map(...))
return REDISCLIENT.get(key).then(function (value) { //<<<<< `return` here causes `.map()` to build an array of promises.
out[key] = value;
});
})).then(function() { //<<<< here's an additional `.then()` chained to `when.all(...)`
return out; //<<<<< `return` here makes the populated `out` available to the `.done()` callback below.
});
})
.catch(console.log)
.done(function (out_) {
console.log(out_);
});
The ugly outer member has disappeared!
In the .done() callback, I have changed the member name to out_ in order to emphasize that it is passed as a consequence of that return out, which happens only when all [geddit] the promises returned by REDISCLIENT.get() calls have successfully settled.
The forEach loop completes, but the Promises you create inside don’t. when.js implements the Promises/A+ spec, which guarantees asynchronous resolution of promise callbacks. That means, that the callback passed to then() is guaranteed to be invoked after the current call stack has finished to execute.
You need to return a promise from your then-callback in order to connect the inner promises to the outer promise. More specifically, you need a promise over all inner promises.
The easiest way to do that is to use when.all:
REDISCLIENT.keys("apikey:*")
.then(function (replies) {
return when.all(replies.map(function (key, index) {
return REDISCLIENT.get(key);
}));
})
.catch(console.log)
.done(function (out) {console.log(out); });
In your original code, you also don't register a callback to done, but call console.log(out) immediately, before the first promise has even resolved.
I have node.js app with 3 functions, each one has own interval (or can by fired by another event):
processOrders
clearExpiredOrders
clearCancelledOrders
Functions are working on shared data, so only one of them can access this data at the time.
When there is a collision, it should wait until the actual function ends and then run second function immediately, but only once.
For example, if there is processOrders running and clearCancelledOrders is triggered 100 times, it will run clearCancelledOrders only once after processOrders finishes its work.
How would you solve this in node.js? Or do you have another idea how to solve this task?
Here is an example of how to do this very simply with promises:
var p = Promise.resolve(); // assuming Bluebird promises or Node 0.11.13+ with Promises.
// create a new empty resolved promise.
function doFirst(act){
return p = p.then(function(){
return someFirstAction(act); // assumes someFirstAction returns a promise
})
}
function doSecond(act){
return p p.then(function(){
return someSecondAction(act); // assumes someFirstAction returns a promise
})
}
What this does is queue the operations on the single chain. When it resolves the chain resolves. It also returns the returned promise, so you can unwrap it and get the value.
For example:
doFirst(1);
doSecond(2);
// some point in the future
doFirst(3).then(function(value){
// both doFirst(1) and doSecond(2) are done here
// the value argument is the resolution value of doFirst(3)
});
If you're unsure on how to convert your API to promises - see this question.
Since, you also want to limit the number of times a particular action is run, you can create special methods for it:
doFirst.queued = false;
function doFirst(act){
if(doFirst.queued) return; // already queued
doFirst.queued = true;
return p = p.then(function(){
return someFirstAction(act).finally(function(){
doFirst.queued = false;
});
})
}
doSecond.queued = false;
function doSecond(act){
if(doSecond.queued) return; // already queued
doSecond.queued = true;
return p = p.then(function(){
return someSecondAction(act); // assumes someFirstAction returns a promise
}).finally(function(){
doSecond.queued = false;
});
}