Nested Promise in Promise each - javascript

Can someone explain why this doesn't work as I expect it?
I am trying to return only the id of the created or updated items to use in the Promise.each().then(). This is necessary because create returns an object and update an array of objects.
Promise.each(items, function(item){
if(...item doesn\'t exist...)
return Item.create(item)
.then(function (created) {
return created.id;
///HOW DO I GET THIS ID ONLY?
});
else {
return Item.update(item)
.then(function (updated) {
return updated[0].id;
///AND THIS ID?
});
}
}).then(function(result) {
sails.log("THIS SHOULD BE AN ID" + JSON.stringify(result));
});
result is the whole created object, instead of just the id. I want to return an array of just the updated ids. Apparently nesting promises is bad, but I don't know how i could simplify this.

Don't use .each use .map to map a list of items to a list of items:
Promise.map(items, function(item){
if(...item doesn\'t exist...)
return Item.create(item).get("id"); // get is a shortcut to .then(fn(x){ r x.id; })
else {
return Item.update(item).get(0).get(id);
}).then(function(result) {
sails.log("Here is the array of all IDs + JSON.stringify(result));
});
If you want to go over them one by one and wait for them, you can chain a second .map. If you want to set to do them sequentially (much slowe) - .map also takes a concurrency parameter.
As Esailija said - in 3.0 the behavior of each changes to return the results so your original code would have been ineffective but would have actually worked in 3.0 .

Related

Cypress - have to use forEach loop with array returned by function in describe block

I have a function which returns array
getOffersCategories() {
cy.get(offersHeaders).then($els => {
cy.wrap(Cypress._.map(Cypress.$.makeArray($els), 'innerText')).as("categories")
})
return this;
}
Now I need to call this function and use array with forEach block having multiple test cases (something like this)
offersCategories.forEach((item, index) => {
it("Validate Max Carousels Limit, Left Right Swipe, See All link of " + item, function ()
{
...
}
})
For me it works fine if I array value is declared in same spec file Like var offersCategories = ["a", "b",c"] or fetched from fixtures, but not finding any way to use the same functionality when array details fetched from function.
Thanks in advance :)

JavaScript promises not passing data to .then unless I explicitly use Promise.resolve

My code gets a translation from Google translate, pushes the translation into an array, and then passes the translation array to the next code block. Or it doesn't, depending on the syntax. This works:
translate.translate(text, target)
.then(function(results) {
translation = results[0];
translationArray.push(translation);
return Promise.resolve(translationArray);
})
.then(function(translationArray) {
console.log(translationArray);
This code doesn't work:
translate.translate(text, target)
.then(function(results) {
translation = results[0];
return translationArray.push(translation);
})
.then(function(translationArray) {
console.log(translationArray);
translationArray logs as 1. What does 1 mean? Why do I need to explicitly use Promise.resolve?
I tried this, it doesn't help:
const finalPromise = translate.translate(text, target)
.then(function(results) {
translation = results[0];
return translationArray.push(translation);
})
.then(function(translationArray) {
console.log(translationArray);
Array#push returns the new length of the array. If your array is empty then after adding an element the new length is 1. That's where the 1 comes from.
You don't have to use Promise.resolve, you just need return translationArray;.

Return a object from two nested queries using promises with nodejs

I need to build a object from the result of two queries but i'm getting undefined. From each object returned at 1st query, I need to set a list with 2nd query. But i getting difficult working with promises when I need to do some implementation nested with the result of chaining queries.
Both queries are working and returning the correct values.
My problem here are involving some logic and "not knowing how to work with javascript and promises" issues.
I appreciate any tips
my code:
var aFunction = function(){
//this query return a list of A objects
return myDAO.getADataList()
.then(function(aDataList){
aDataList.forEach(function(aData){
//this query return a list of B objects to each A object
myDAO.getBdataFromA(aData.id)
.then(function(bDataList){
//here i want to return a object with both values
return {
aValue: aData,
list : bDataList
}
})
})
});
}
aFunction()
.then(function(data){
//here data is undefined
console.log(data);
});
You can use Promise.all:
return myDAO.getADataList()
.then(function(aDataList){
return Promise.all(
aDataList.map(function (aData) {
//this query return a list of B objects to each A object
return myDAO.getBdataFromA(aData.id)
.then(function (bDataList) {
//here i want to return a object with both values
return {
aValue: aData,
list: bDataList
}
})
})
);
});
The problem is you are not return anything at the second promise that's why you get undefined.
.then(function(aDataList){
aDataList.forEach(function(aData){
myDAO.getBdataFromA(aData.id)
To solve this you need to return value by aggregating result from myDAO.getBdataFromA. You can use Array.map and Promise.all to do that.
a var aFunction = function(){
//this query return a list of A objects
return myDAO.getADataList()
.then(function(aDataList){
var getaDataListPromises = aDataList.map(myDAO.getBdataFromA(aData.id)
.then(function(bDataList){
//here i want to return a object with both values
return {
aValue: aData,
list : bDataList
}
}));
return Promise.all(getaDataListPromises);
});
}

walking a tree with Promises

I have a tree structure that I want to walk using Promises, and I haven't figured out the right code pattern for it.
Assume that we're given node names, and the act of converting a node name to a node is an asynchronous process (e.g. involves a web access). Also assume that each node contains a (possibly empty) list of children names:
function getNodeAsync(node_name) {
// returns a Promise that produces a node
}
function childrenNamesOf(node) {
// returns a list of child node names
}
What I want to end up with a method with this signature:
function walkTree(root_name, visit_fn) {
// call visit_fn(root_node), then call walkTree() on each of the
// childrenNamesOf(root_node), returning a Promise that is fulfilled
// after the root_node and all of its children have been visited.
}
that returns a Promise that's fulfilled after the root node and all of its children have been visited, so it might be called as follows:
walkTree("grandpa", function(node) { console.log("visiting " + node.name); })
.then(function(nodes) { console.log("found " + nodes.length + " nodes.")});
update
I've create a gist that shows my first attempt. My (slightly buggy) implementation for walkTree() is:
function walkTree(node_name, visit_fn) {
return getNodeAsync(node_name)
.then(function(node) {
visit_fn(node);
var child_names = childrenNamesOf(node);
var promises = child_names.map(function(child_name) {
walkTree(child_name, visit_fn);
});
return Promise.all(promises);
});
};
This visits the nodes in the correct order, but the outermost Promise resolves before all the sub-nodes have been visited. See the gist for full details.
And as #MinusFour points out, using this technique to flatten the list of nodes is rather pointless. In fact, I really just want the final promise to fire when all the nodes have been visited, so a more realistic use case is:
walkTree("grandpa", function(node) { console.log("visiting " + node.name); })
.then(function() { console.log("finished walking the tree")});
Well it isn't much of a problem to handle a function call for each node, but gathering the node values is kind of a problem. Kind of hard to walk that tree, your best bet would be to map it to a tree with no eventual values. You could use something like:
function buildTree(root_name) {
var prom = getNodeAsync(root_name);
return Promise.all([prom, prom.then(function(n){
return Promise.all(childrenNamesOf(n).map(child => buildTree(child)))
})]);
}
From there on you do:
var flatTree = buildTree(root_name).then(flatArray);
flatTree.then(nodes => nodes.forEach(visit_fn));
flatTree.then(nodes => whateverYouWantToDoWithNodes);
To flatten the array you could use:
function flatArray(nodes){
if(Array.isArray(nodes) && nodes.length){
return nodes.reduce(function(n, a){
return flatArray(n).concat(flatArray(a));
});
} else {
return Array.isArray(nodes) ? nodes : [nodes];
}
}
To be honest, it's pointless to have the tree walker if you want a list of nodes you are better up flattening it up and then iterating the elements, but you can walk the array tree if you want.
Despite what I said in the O.P, I don't really care about the return values of the final promise, but I do want to wait until all the nodes have been traversed.
The problem with the original attempt was simply a missing return statement in the map() function. (Despite appearances, this is essentially structurally identical to #MinusFour's answer.) Corrected form below:
function walkTree(node_name, visit_fn) {
return getNodeAsync(node_name)
.then(function(node) {
visit_fn(node);
var child_names = childrenNamesOf(node);
var promises = child_names.map(function(child_name) {
return walkTree(child_name, visit_fn);
});
return Promise.all(promises);
});
};
Here are two use cases for walkTree(). The first simply prints the nodes in order then announces when the tree walk is finished:
walkTree("grandpa", function(node) { console.log("visiting " + node.name); })
.then(function() { console.log("finished walking the tree")});
The second creates a flat list of nodes, made available when the tree walk completes:
var nodes = [];
walkTree("grandpa", function(node) { nodes.push(node) })
.then(function() { console.log('found', nodes.length, 'nodes);
console.log('nodes = ', nodes); });

Executing an ElementArrayFinder

I am learning protractor and it has thus far been a wild journey because I am also pretty new to Javascript. I learned so far that protractor queues all promises and they can be executed using then().
However, I am now trying to use a filter() on an ElementArrayFinder but it doesn't seem to execute. Only when I preprend it with the return-keyword, the filter get's executed, but then I leave my function and I don't want that.
Can someone help me in understanding this please?
Below my code:
it('Select service', function() {
servicePage.services.filter(function(elem, index) {
return elem.getAttribute('class').then(function(attribute) {
console.log('*****' + attribute);
return attribute === 'service passive';
});
});
servicePage.services.get(0).element(by.tagName('input')).click();
});
When running above, the console log is not performed so I guess the filter function is not being executed. When I do it like below, the filter is executed but then the click() is not performed.
it('Select service', function() {
return servicePage.services.filter(function(elem, index) {
return elem.getAttribute('class').then(function(attribute) {
console.log('*****' + attribute);
return attribute === 'service passive';
});
});
servicePage.services.get(0).element(by.tagName('input')).click();
});
Example3:
it('Select service', function() {
servicePage.services.filter(function(elem, index) {
return elem.getAttribute('class').then(function(attribute) {
console.log('*****' + attribute);
return attribute === 'service passive';
});
}).first().element(by.tagName('input')).click();
});
Thanks in advance!
Regards
You should catch the element that filter function returns and then perform action on it. filter() function returns elements that match the criteria you specify in the function. In your case its returning an element that has a class attribute service passive. If there are more than one elements with same attribute, then you probably have to chain get() function to perform operation on the required element. Here's how -
servicePage.services.filter(function(elem, index) {
return elem.getAttribute('class').then(function(attribute) {
console.log('*****' + attribute);
return attribute === 'service passive';
});
}).element(by.tagName('input')).click(); //if only one element is returning, click on it
OR replace the last line with below line when there are more elements that match the criteria -
}).get(1).element(by.tagName('input')).click(); //click the second element in the array of elements returned
Hope it helps.

Categories

Resources