Promise Resolve Object giving empty Object - javascript

so in my Node file, I take some data from a Mongoose database, perform some operations and I want a dictionary like object at the end. Now my Mongoose database has only two objects I want, so I use the aggregate command to give me those. Then, I loop through the results, and add them to a dictionary. Then, I want to display something from the dictionary. Due to the asynchronous nature of all this, I was forced to learn/use promises. Therefore, this is the code that I generated:
function f1(){
return new Promise(resolve=>{
collectionName.aggregate([
{
$group:
{
_id: "$ids",
property: {$max: "$properties"}
}
}
]).then(data=>{
for(i=0; i<data.length; i++){
dictionary[data[i]['_id']] = data[i]['property'];
}
});
resolve(dictionary);
});
};
async function f2(){
var dict = await f1()
console.log(dict)
f1();
f2();
when I put a console.log into the for loop, I get the data I want in that it has the id and the property in dictionary form. However, when I run f2, I only get {} as the output. Does anyone know why?

With the resolve(dictionary) being outside of the aggregate() 's then(), it executes before dictionary is populated, hence the empty object. Try moving the resolve(dictionary) to inside the then() of aggregate() instead to return a promise with value of dictionary. Effectively you are attempting to chain promises. Make sure to also return the collectionName.aggregate() as a whole as well to ensure the entire promise is returned from f1().
function f1() {
return new Promise(resolve => {
return collectionName.aggregate([{
$group: {
_id: "$ids",
property: {
$max: "$properties"
}
}
}]).then(data => {
for (i = 0; i < data.length; i++) {
dictionary[data[i]['_id']] = data[i]['property'];
}
return resolve(dictionary);
});
});
}
Hopefully that helps!

Related

Javascript Run Async Code For Each Item Of For Loop With Promises [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 5 years ago.
I have a function that gets's an object passed into it, with key and data that is an array.
I have to call the API to get additional information, which in turn are added back into the object and the whole object is returned.
My first approach was incorrect because I was trying to pass the data out of the .then(), but that was wrong practice.
function asignChecklistItems(taskArray) {
// get all the people's tasks passed in
return new Promise(function(resolve, reject) {
var promises = []
// Task Array: {"Person":[tasks,tasks,tasks]}
for (var person in taskArray) {
var individualTaskPerson = taskArray[person]
// get the person's tasks
for (var individualTask in individualTaskPerson) {
// here we get each individual task
var task = individualTaskPerson[individualTask]
// Check if the checklist is present, the .exists is a bool
if (task.checklist.exists === true) {
//Here we push the promise back into the promise array
// retrieve is the async function
promises.push( retrieveCheckListItems(task.checklist.checklistID)
.then(checklistItems => {
var complete = []
var incomplete = []
const items = checklistItems["checkItems"];
for (var each in items) {
const item = items[each]
if (item["state"] === "complete") {
complete.push(item["name"])
} else {
incomplete.push(item["name"])
}
}
task.checklist.completeItems.push(complete)
task.checklist.incompleteItems.push(incomplete)
return taskArray // used to be: resolve(taskArray) See **EDIT**
})
.catch(err => {
logError(err)
reject(err)
})
)
} else {
// There's no checklist
}
}
}
Promise.all(promises).then(function(x){
// Here when checked, all the items are pushed to the last person inside the array.
// Eg. {PersonA:[tasks],PersonB:[tasks],PersonC:[tasks]}
// All of the complete and incomplete items are added to Person C for some reason
resolve(taskArray)
})
})
}
I've tried many approaches, returning the entire promise, trying to return from within the promise (which didn't work because it's not allowed), and trying to run the async code earlier, and moving the for loops into the promise. None of them worked, this is the closest, where it returns it for PersonC.
This was mostly based on other SO questions such as this one, which showed how to use Promise.All.
Is this the preoper way of calling a promise (async function) for each element of a for loop?
EDIT:
Another mistake that was in the code, is that if there is a promise insde a promise, such as the retrieveCheckListItems inside of the asignCheckListItems, it shouldn't resolve itself, but it should return the value. I updated the code to reflect that, based on the working production code.
Apparently another problem I was
You are executing task.checklist.completeItems.push(complete) in the retrieveCheckListItems .then, which means the code is asynchronous.
At the same time, var task is instead being assigned in a for...in loop, which means that by the time your .then is triggered, the for...in iteration will be complete and task will be the last assigned object.
Note that var variables have a function scope, which basically means that your code is equivalent to:
function asignChecklistItems(taskArray) {
// get all the people's tasks passed in
return new Promise(function(resolve, reject) {
var promises = []
var individualTaskPerson;
var task;
...
Fix it by:
Either changing var task to let task (if you are using ES6). This then creates the variable within each for loop, rather than within the enclosing function.
or
By replacing
for (var individualTask in individualTaskPerson) {
// here we get each individual task
var task = individualTaskPerson[individualTask]
with
Object.keys(individualTaskPerson).forEach(function(individualTask) {
var task = individualTaskPerson[individualTask];
...
});
   
Do the same for the other for...in loops and variables.

What does .push.bind do?

I'm trying to learn coding in Javascript and came across this question and have a few questions. What does results.push.bind(results) do in this question? Please see question below:
Suppose getData is a function that takes a query object and returns a promise for the result of the query. Suppose also that someArrayOfQueries is an array of query objects. Explain what would be printed by the following code and why:
function runMultipleQueries(queries) {
var results = [];
queries.forEach(doQuery);
return results;
function doQuery(query) {
getData(query)
.then(results.push.bind(results));
}
}
function log(value) {
console.log(value);
}
runMultipleQueries(someArrayOfQueries).forEach(log);
In Javascript, unlike in other Object Oriented languages, the variable this is quite complicated and can be changed many times.
When working with arrays, the function push takes the array it is called "on", and puts a new element to the end of it. In this case, the push function knows what array it is working on by reading the this variable.
Imagine this example code:
var myDataStructure = {};
myDataStructure.value = 0;
function addOneToValue() {
this.value += 1;
}
myDataStructure.addOne = addOneToValue;
myDataStructure.addOne(); // here - the variable `this` == myDataStructure
However, if you call the function addOneToValue with the code addOneToValue(), the value of this is not in fact myDataStructure. It is undefined, or a global object like window.*
To manually force addOneToValue to always use myDataStructure as the this value, you can do the following:
addOneToValue = addOneToValue.bind(myDataStructure);
Now you can safely call addOneToValue(), since the this value was bound to myDataStructure.
In your code, runMultipleQuery is passing the function to another handler, who will not call the function like results.push. That means when the function is called, push will again have an uncertain this value. However, by calling bind, we can ensure that runMultipleQuery calls the push function and forces this to be the results variable.
What results.push.bind(results) does is it forces the method call of push to use the scope of results.
TLDR;
results is an Array. results.push() is a call to Array#push, but by passing the function results.push directly to the Promise Promise#then it will be called within a different scope.
That is, the keyword this will reference a different object.
By using bind (e.g. - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind), the scope will be "bound" to a specific Object.
The reason for this is because JavaScript is a prototype language, rather than an object-oriented language.
What does .push.bind do?
Returns the .length of the array, when called
function runMultipleQueries(queries) {
var results = [];
return Promise.all(queries.map(doQuery));
function doQuery(query) {
return Promise.resolve(query)
.then(results.push.bind(results))
}
}
function log(value) {
console.log(value);
}
runMultipleQueries([10, 20, 30]).then(log);
Note, that as astutely observed and alerted to by #Bergi, the pattern .then(results.push.bind(results)) actually returns the .length of the array.
One solution which still includes the use of the pattern is to chain a second .then() to get the .length returned by previous .then() and return the element of the array by subtracting 1 from the value and using bracket notation
function runMultipleQueries(queries) {
var results = [];
return Promise.all(queries.map(doQuery));
function doQuery(query) {
return Promise.resolve(query)
.then(results.push.bind(results))
.then(len => results[len - 1]);
}
}
function log(value) {
console.log(value);
}
runMultipleQueries([10, 20, 30]).then(log);
though creating results array is not necessary when using Promise.all(), which returns an array of values
.forEach() alone will not await the previous result of getData() call, results will probably be an empty array at runMultipleQueries(someArrayOfQueries).forEach(log);
Use Promise.all() and .map() instead of .forEach(), return the Promise from getData() call
function runMultipleQueries(queries) {
return Promise.all(queries.map(doQuery));
function doQuery(query) {
return new Promise(function(resolve) {
setTimeout(function() {
resolve(query)
}, Math.floor(Math.random() * 1000))
})
}
}
function log(value) {
console.log(value);
}
runMultipleQueries([1,2,3]).then(log);

How do I return a response from multiple get requests, inside an async.eachSeries loop, inside a function

I am attempting to return a new array, created by combining arrays from several get requests. The new array returns correctly within the async.eachSeries async library function, however the parent function returns undefined.
My understanding is that the return within the parent function is running synchronously with the async.eachSeries() (before it can finish), which causes the parent function to return undefined.
var array_strings = [ “someUrl”, “someUrl”, “someUrl” ]
function get_forEachUrlString(base_url, array_strings) {
…
var combinedArray = [];
var mergedArray = [];
…
async.eachSeries(array_strings, function(item, done) {
…
$.get(bas_url+item, function(data) {
…
combinedArray.push(data)
done(); // stopping the async looping
mergedArray = [].concat.apply([], combinedArray); // Merging the array of combined arrays
});
}, function(err) {
if (err) {
throw err;
}
console.log("All requests are done");
console.log(mergedArray); // logs correct array
return mergedArray
});
console.log(mergedArray); // logs [] empty array
return mergedArray // Hoping this would return the newly created mergedArray
}
get_forEachUrlString(“someBaseUrl”, array_strings) // Attempting to return mergedArray
// this function tries to call the mergedArray, but returns undefined
function initialRender() {
…
console.log(merged) // returns undefined
}
I have been searching for a way to get the parent function get_forEachUrlString() to return the value produced by the async.eachSeries function. I believe I will need to run a callback, but after trying several different combinations I’m still stumped.
How can I get the new array I’ve created, mergedArray to return from the function get_forEachUrlString()?
EDIT: I failed to mention that I am attempting to call the returned value from get_forEachUrlString() within initialRender(). See how I fixed this in my answer below.
After some trial and error I was able to get this working. I was attempting to place any number of callbacks, but what I really needed to do was place the initialRender() function within the get_forEachUrlString() function and pass the new mergedArray value to the initialRender() function.
So the proper setup looks like this.
function get_forEachUrlString(base_url, array_strings) {
…
async.eachSeries(array_strings, function(item, done) {
…
}, function(err) {
…
mergedArray = [].concat.apply([], combinedArray);
initialRender(mergedArray);
});
}
get_forEachUrlString(“someBaseUrl”, array_strings)
Then the initialRender() returns what it should.

How do I mix all this logic with javascript Promises?

I'm using bluebird in Node, and I'm still pretty new to using Promises, especially when things start getting beyond the basics.
Here's a function I need to construct using Promises, and I'm struggling to figure out the best way to set it up. At a high level, this function will take a model object, and return it, converting any query properties to their result sets. For example, a property can have a value of "query(top5Products)", and we'll need to lookup that named query and replace the value with the results of that query. Properties can also be an actual string-based query (using RQL, e.g. "eq(contentType,products)&&limit(5,0)") This converted model object will then be used to bind against a template.
Here's my pseudo-coded function, currently synchronous except for the calls to existing promise-returning services...
function resolveQueryPropertiesOnModel(model) {
for (let property in model) {
if (model.hasOwnProperty(property)) {
let queryName = this.getNameOfNamedQuery(model[property]); // will return undefined if the property is not a named query
if (queryName) {
// this property is a named query, so get it from the database
this.getByName(queryName)
.then((queryObject) => {
// if queryObject has a results propery, that's the cached resultset - use it
if (queryObject && queryObject.results) {
model[property] = queryObject.results;
}
else {
// need to resolve the query to get the results
this.resolve(queryObject.query)
.then((queryResults) => {
model[property] = queryResults;
});
}
};
}
else if (this.isQuery(model[property]) { // check to see if this property is an actual query
// resolve the query to get the results
this.resolve(model[property])
.then((queryResults) => {
model[property] = queryResults;
});
}
}
}
// return some sort of promise that will eventually become the converted model,
// with all query properties converted to their resultsets
return ???;
}
I'm still very rusty when it comes to taking loops with logic and some pre-existing promises and mashing them all together.
Any help will be appreciated.
Here's an implementation of your code using Bluebird that makes these structural changes:
Runs the outer for loop and collects any promises that were started
Returns nested promises to chain them so they are linked and so the top level promise will indicate when everything is done in that chain
Collects any new promises into the promises array
Uses Promise.all(promises) to track when all the async promise operations are done and returns that.
It appears your result is the side effect of modifying the models object so no explicit values are returned through the promises. You can use the returned promise to know when all the async operations are done and you can then examine the model object for results.
Code:
function resolveQueryPropertiesOnModel(model) {
const promises = [];
for (let property in model) {
let p;
if (model.hasOwnProperty(property)) {
let queryName = this.getNameOfNamedQuery(model[property]); // will return undefined if the property is not a named query
if (queryName) {
// this property is a named query, so get it from the database
p = this.getByName(queryName).then((queryObject) => {
// if queryObject has a results propery, that's the cached resultset - use it
if (queryObject && queryObject.results) {
model[property] = queryObject.results;
} else {
// need to resolve the query to get the results
return this.resolve(queryObject.query).then((queryResults) => {
model[property] = queryResults;
});
}
};
} else if (this.isQuery(model[property]) { // check to see if this property is an actual query
// resolve the query to get the results
p = this.resolve(model[property]).then((queryResults) => {
model[property] = queryResults;
});
}
}
// if we started a new promise, then push it into the array
if (p) {
promises.push(p);
}
}
return Promise.all(promises);
}
This is how I would solve it.
a q.all() will be resolved if all of the promises are resolved. each promise is one property in the model that is processed.
for each property (I'd use a library like lodash and _.reduce, but you can use the hasOwnProperty if you like). anyway, foreach property, resolveModelProperty function returns a promise that decides the fate of the property, if there is a query name, get it, if not and there is a query, resolve it, if not, don't change the property.
to helper functions, resolveByName and resolveQuery will handle the case of cached and uncached queries.
function resolveQueryPropertiesOnModel(model) {
const promises = [],
resolveQuery = toBeResolved => this.resolve(toBeResolved),
resolveByName = queryName => this.getByName(queryName)
.then(queryObject => queryObject && queryObject.results
? queryObject.results : resolveQuery(queryObject.query)),
resolveModelProperty = (modelProperty) => {
const queryName = this.getNameOfNamedQuery(modelProperty);
return queryName ? resolveByName(queryName) :
this.isQuery(modelProperty) ? resolveQuery(modelProperty):
modelProperty;
};
for(let property in model)
if( model.hasOwnProperty(property)
promises.push(resolveModelProperty(model[property])
.then(result=> model[property]=result));
return q.all(promises);
}

Retry a promise step

Suppose I have the the following Promise chain:
var result = Promise.resolve(filename)
.then(unpackDataFromFile)
.then(transformData)
.then(compileDara)
.then(writeData);
Now I have not only one transformData function but two or more, stored in an array. I want to try the first one, and if the compileData function fails, try the second one and so on until either compileData succeeds or the array of transformData functions is exhausted.
Can someone give me an example on how to implement this?
Running all transformData functions and give the result array to compileData is not an option, since the functions are very expensive and I want to run as few as possible of them.
transformData itself also returns a Promise, if that helps.
I would start by isolating the notion of trying a number of promises until one succeeds:
function tryMultiple([promise, ...rest]) {
if (!promise) throw new Error("no more to try");
return promise.catch(() => tryMultiple(rest));
}
Now write a handler which tries each combination of transforming and compiling:
function transformAndCompile(transformers) {
return function(data) {
return tryMultiple(transformers.map(t => t(data).then(compileData)));
};
}
Now the top level is just:
var result = Promise.resolve(filename)
.then(unpackDataFromFile)
.then(transformAndCompile(transformers))
.then(writeData);
By the way, Promise.resolve(filename).then(unpackDataFromFile) is just a roundabout way of saying unpackDataFromFile(filename).
You can do something like this:
// various transformer functions to try in order to be tried
var transformers = [f1, f2, f3, f4];
function transformFile(filename) {
// initialize tIndex to select next transformer function
var tIndex = 0;
var p = unpackDataFromFile(filename);
function run() {
return p.then(transformers[tIndex++])
.then(compileData)
.catch(function(err) {
if (tIndex < transformers.length) {
// execute the next transformer, returning
// a promise so it is linked into the chain
return run();
} else {
// out of transformers, so reject and stop
throw new Error("No transformer succeeded");
}
}).then(writeData);
}
return run();
}
transformFile("someData.txt").then(function(finalResult) {
// succeeded here
}).catch(function(err) {
// error here
});
Here's how this works:
Sets up a tIndex variable that indexes into the array of transformer functions.
Calls unpackDataFromFile(filename) and saves the resulting promise.
Then executes the sequence p.then(transformer).then(compileData) using the first transformer. If that succeeds, it calls writeData and returns the resulting promise.
If either the transformer or compileData fails, then it goes to the next transformer function and starts over. The key here to making this work is that in the .catch() handler, it returns a new promise which chains into the originally returned promise. Each new call to run() is chained onto the original promise from unpackDataFromFile() which allows you to reuse that result.
Here's a bit more generic implementation that makes an iterator for an array that iterates until the iterator callback returns a promise that fulfills.
// Iterate an array using an iterator that returns a promise
// Stop iterating as soon as you get a fulfilled promise from the iterator
// Pass:
// p - Initial promise (can be just Promise.resolve(data))
// array - array of items to pass to the iterator one at a time
// fn - iterator function that returns a promise
// iterator called as fn(data, item)
// data - fulfilled value of promise passed in
// item - array item for this iteration
function iterateAsyncUntilSuccess(p, array, fn) {
var index = 0;
function next() {
if (index < array.length) {
var item = array[index++];
return p.then(function(data) {
return fn(data, item).catch(function(err) {
// if this one fails, try the next one
return next();
});
});
} else {
return Promise.reject(new Error("End of data with no operation successful"));
}
}
return next();
}
// Usage:
// various transformer functions to try in order to be tried
var transformers = [f1, f2, f3, f4];
iterateAsyncUntil(unpackDataFromFile(filename), transformers, function(data, item) {
return item(data).then(compileData);
}).then(writeData).then(function(result) {
// successfully completed here
}).catch(function(err) {
// error here
});
The following should do what you want most idiomatically:
var transformers = [transformData, transformData2];
var result = unpackDataFromFile(filename)
.then(function transpile(data, i = 0) {
return transformers[i](data).then(compileData)
.catch(e => ++i < transformers.length? transpile(data, i) : Promise.reject(e));
})
.then(writeData);
Basically you recurse on the transformers array, using .catch().

Categories

Resources