I'm trying to return an object with a specific structure but i can't return the object.
As you can see in the code if i print inside promise it prints data but if i print outside is empty... why?
How can i return the object parts?
var parts = [];
var part = [];
var articles = [];
part.push(request.query("SELECT u_order, u_familia, u_part, u_type FROM u_part (nolock) where u_order <'92' and u_order <> '100'"));
return Promise.all([Promise.all(part)]).then(function(listOfResults)
{
for(var i=0; i<listOfResults[0][0].length; i++)
{
articles.push(request.query("SELECT st.u_posic, sc.ref, sc.qtt, sc.design FROM st INNER JOIN sc ON st.ref = sc.ref where sc.ststamp ='"+stamp+"' and st.u_posic = '"+listOfResults[0][0][i].u_order+"'"));
//articles.push(request.query("SELECT ststamp, ref, design FROM st (nolock) WHERE u_posic = '"+listOfResults[0][0][i].u_order+"'"));
Promise.all([Promise.all(articles)]).then(function(listOfArticles)
{
for(var j=0; j<listOfResults[0][0].length; j++)
{
parts.push({
u_order: listOfResults[0][0][j].u_order,
u_familia: listOfResults[0][0][j].u_familia,
u_part: listOfResults[0][0][j].u_part,
u_type: listOfResults[0][0][j].u_type,
articles: listOfArticles[0][j-1]
});
console.log(parts); HERE THE OBJECT HAD DATA
}
});
}
console.log(parts); BUT HERE IS EMPTY
}).catch(function(err)
{
console.log(err);
});
Thank you.
The whole point of Promises is being asynchronous. You need to wait for your Promise to resolve or reject to check for data. Specifically, you should only do anything with the returned value of your Promise within the Promise.then() method, which runs as soon as the Promise finishes execution. Changing variables which exist outside the Promise from within it, as you are doing by calling parts.push() is a bad practice. You should return parts within your promise, and then, in a final .then() callback, you should do whatever is required with parts.
Something like:
Promise.all(part).then(makeAllQueries).then(doSomethingWithQueryResults);
You can only be sure you actually have data or not when your final .then callback runs.
Also, doing Promise.all([Promise.all(part)]) does nothing for you, as you're just wrapping your returned array in one more array level.
Related
This should be simple, but it doesnt make sense to me.
It is a simple section of a promise chain
let flightName = [];
let guidArr = [];
Promise.all(guidArr)
.then(values => {
for(var a = 0; a < values.length; a++) {
Xrm.WebApi
.online
.retrieveRecord("crd80_flightplanevent", values[a], "?$select=_crd80_learnerflight_value")
.then(
function success(result) {
flightName.push(result["_crd80_learnerflight_value#OData.Community.Display.V1.FormattedValue"])
},
function(error) {
DisplayError(error)
});
}
return flightName
}).then(flightName => {
console.log(flightName)
console.log(flightName.length)
return flightName
})
The console displays the flightName array correctly,
but flightName.length is always consoled as 0, even though console.log(flightName) puts length:2 as the output
Why???
I need to work with each item in the array but it is not recognised correctly
The recommendation by #derpircher worked:
You are not awaiting the promises in your for(var a = 0; a < values.length; a++) loop. Thus, when you do return flightName the array is still empty, and thus console.log(flightName.length) prints 0. The output from console.log(flightName) might pretend, that flightName contains values, but actually it does not because that console.log is referencing the object, which is later filled when the promises resolve. Do console.log(JSON.stringify(flightName)) and you will see it prints just [] because at that moment, the array is still empty
To resolve that issue, make the first then handler async and use await Xrm.WebApi... or properly wrap that whole thing in a Promise.all
I am thinking about a scenario of building up a promise queue:
//Let's assume that promises is an array of promises
var promiseQueue = [];
for (var promise of promises) {
if (promiseQueue.length) promiseQueue[promiseQueue.length - 1].then(promise);
promiseQueue.push(promise);
}
I am thinking about implementing a function called resolver:
function *resolve() {
var promise;
while (promise = yield) Promise.resolve(promise);
}
and then iterating it:
var promiseGenerator = resolve();
The problem is the for..of here which would be responsible for the actual iteration:
for (var r of promiseGenerator) {
}
At the code above the generator will be successfully iterated, but unfortunately I am not aware of a way to successfully pass a parameter to this generator at the iteration of for..of.
I would like to clarify that I do not need an alternative, I am perfectly aware that we can do something like this:
for (var p in promiseQueue) promiseGenerator.next(promiseQueue[p]);
I am specifically interested to know whether I can pass parameters to the generator when I execute a for..of cycle.
EDIT
The problem raised by amn is that in the example he/she was focusing on would always get undefined. That's true if we pass undefined to next(), but not true if we pass something else. The problem I was raising is that a for..of loop does not allow us to pass anything to yield, which is this specific question is all about, the example is a mere illustration of the problem, showing that the promises we would create will never be created in a for..of loop. However, there is life for Iterable objects outside the realm of for..of loops and we can pass defined values into the yield. An example with the criticized code chunk can look like:
function *resolve() {
var promise;
while (promise = yield) Promise.resolve(promise);
}
var responses = [];
var f = resolve();
var temp;
for (var i = 10; !(temp = f.next(i)).done; i--) responses.push(temp);
As we can see above, the yield above cannot be assumed ab ovo to be undefined. And of course we can pass some custom thenables, like
Promise.resolve({
then: function(onFulfill, onReject) { onFulfill('fulfilled!'); }
});
or even promises which were not resolved yet. The point of the example was to show that we cannot pass values to the yield using the for..of loop, which is quite a feature gap in my opinion.
No, it is not possible to pass arguments to next.
function* generateItems() { /* ... */ }
for (var item of generateItems()) {
console.log(item);
}
is mostly short for
function* generateItems() { /* ... */ }
var iterator = generateItems()[Symbol.iterator]();
do {
const result = iterator.next();
if (result.done) break;
const item = result.value;
console.log(item);
} while (true);
barring a few missing try/catch wrappers. You can see in the spec here that it calls .next with no arguments:
Let nextResult be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]], « »).
e.g.
iterator.next.apply(iterator, []);
calling next() with an empty array of arguments.
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!
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.
I have ran out of ideas dealing with this nested return situation.
I am trying to find out if the array retrieven from the backend via a promise contains an element in the array.
This works:
function isNameTaken() {
var duplicateFound = 0;
for (var i = 0; i < $scope.entities.length; i++) {
if ($scope.entities[i].name === $scope.myName) {
duplicateFound = 1;
}
}
return duplicateFound;
}
I'd like to adjust this so there is a refresh of the array before the comparison. I have a method for retrieving it that resolves into a promise. Now, I can wrap the whole thing into a .then wrap but I then cannot access my duplicateFound variable from outside to the the async nature of the inner function.
This what I want to get working:
function isNameTaken() {
return loadEntities().$promise.then(function(entities) {
var duplicateFound = 0;
for (var i = 0; i < entities.length; i++) {
if (entities[i].name === $scope.myName) {
duplicateFound = 1;
}
}
return duplicateFound;
});
}
The inner part works flawlessly as the promise resolves, but the function as a whole always resolves to true as the return loadEntities() part does not pass me back the value of my enclosed return inside but the promise itself. And without it, it always resolves to false(I think its an undefined).
So in short. The first one works. How do I get my hands on the value of duplicateFound outside this method?
the function as a whole always resolves to true
No, it does not return true. It does return a promise for a boolean value, which might fulfill with true (or with false or reject altogether). That promise of course is a truthy value, so you cannot use it right inside of an if condition.
Instead, you have to adapt the calling code to anticipate the promise, and let it wait for the resolution before inspecting the value. Of course, this makes your whole function (and everything that calls it) asynchronous.
return isNameTaken().then(function(taken) {
if (taken)
…
else
…
}); // yields another promise for the result of the callback