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
Related
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);
The issue I am having is that I need to get a total count of a collection of objects, which has the form of a tree where each object can also contain deeply nested objects. The collection of data I start with already has one nested layer available, but to find out if there is a third or more nested layers in an object, an API call has to be made with the nested object's id, which returns the next nested object if it exists.
So, currently I have something like this:
function getCount(thread) {
var deferred = $q.defer();
var count = 0;
function getComment(comment) {
count++;
//if nested comments exist in data
if (comment.fld.nested) {
_.each(comment.fld.nested, function(x) {
getComment(x);
});
deferred.resolve(count);
} else if (comment.meta) {
//if not, load more from API with id
return PostService.getComment(comment.meta.id).then(function(comment){
if (comment.fld.nested) {
_.each(comment.fld.nested, function(x) {
return getComment(x);
});
}
return count;
});
}
return deferred.promise;
}
_.each(thread.fld.nested, function(x) {
return getComment(x);
});
return deferred.promise;
}
getCount(c).then(function(x) {
console.log('final count:', x);
});
Right now, I can get the count for all objects nested to 2nd level deep, but anything loaded from the API promise is not included in the count when I call the getCount().then() function. How do I make this wait until all promises are resolved so I can get the final count returned?
As Jaromanda X mentioned in the comments, you're having getComment return an asynchronous promise, but you're not waiting for the results.
One aspect hindering you is that you're using deferred-style promises; if you were to use then, you'll have the benefit of being able to return a promise in a then handler, which will cause the outer promise to wait on the inner promise.
// foo won't resolve until Promise Two resolves.
var foo = getPromiseOne().then(function(valueOne) {
return getPromiseTwo(valueOne);
});
You'll be waiting for a lot of promises in parallel, so I'm going to switch to Promise.all ($q.all) and Array.map (_.map) so it's clear what you're waiting for.
function getCount(thread) {
var count = 0;
function getComment(comment) {
count++;
//if nested comments exist in data
if (comment.fld.nested) {
return $q.all(_.map(comment.fld.nested, function(x) {
return getComment(x);
}));
} else if (comment.meta) {
//if not, load more from API with id
return PostService.getComment(comment.meta.id).then(function(comment){
if (comment.fld.nested) {
return $q.all(_.map(comment.fld.nested, function(x) {
return getComment(x);
}));
}
});
}
return; // Count matters, not return values, so returning undefined is fine.
}
// Wait for all nested promises to resolve, but ignore the array result
// and return the count instead.
return $q.all(_.map(thread.fld.nested, function(x) {
return getComment(x);
}).then(function() { return count; });
}
getCount(c).then(function(x) {
console.log('final count:', x);
});
Try removing the parameter "comment" from the API call then method:
From:
function(comment){}
To:
function(){}
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.
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().
And on top of this, why are $scope values set BEFORE $q.all is called completely ignored?
Function within main controller:
$scope.apply = function (security) {
var entity = shareDataService.getModalEntity();
var depot = shareDataService.getModalDepot();
$scope.loaded = false;
var myDataPromise;
if (entity == "NULL") {
myDataPromise = getDataService.getDataFromREST(security);
} else {
myDataPromise = $q.all(getDataService.keepICorrect(security));
};
myDataPromise.then(function () {
//DO STUFF
}, function errorCallback(response) {
//DO MORE STUFF
});
}
And my keepICorrect() function in my service (which is a closure):
keepICorrect: function (security) {
var promises = [];
for (var i = 0 ; i < entity.length; i++) {
promises.push(this.getDataFromREST(security, i));
}
return promises;
},
However when the $scope.apply() function is executed, nothing happens. Boolean $scope.loaded does not activate on the scope and no exception is thrown (which is what I was expecting). Why is this?
I've edited the code and made my controller function Apply check if entity = "NULL", and this seems to have solved the issue of whether it is an array or not. Still doesn't answer my question as to why if q.all is not returned an array of promises, does nothing happen in the function it is called in, even if before q.all is called.
Pay attention that the Promise library wraps all errors thrown in enclosed brackets code, so you are really recommended to test your functions before wrapping them in a promise mechanism.
In your case:
keepICorrect: function (security) {
var promises = [];
for (var i = 0 ; i < entity.length; i++) {
promises.push(this.getDataFromREST(security, i));
}
return promises;
},
you say that it is a closure, so I figure that this.getDataFromREST should be undefined. Please try to add correct code progressively in order to do not have your errors hidden and remove the error in your code.