I'm trying to do a recursive async loop to trace all the children of a particular object from a third-party lib in nodejs.
Heres the pseudo code:
var tracer = function(nodes){
var promises [];
nodes.forEach(function(node){
// trace returns a promise ...
var promise = builder.trace(node)
promises.push(promise);
promise.then(function(tree){
// if we had children, get those
if(tree.children.length){
promises.push.apply(promises, tracer(tree.children));
}
});
});
return promises;
};
RSVP.all(tracer(myArr)).then(function(allTrees){ ... });
but I can't put my finger on how to get them all to resolve correctly and returns the results in one array.
You must not push the recursive promises on the array in the delayed callback. Instead, you'll need to push a promise that represents the recursive results (resolves with those delayed produced promises) right away. Luckily, you even get exactly that back from that then call.
Additionally, I would swap out the each for a map, and do RSVP.all immediately inside the function, for not expecting the caller to deal with that.
function tracer(nodes){
var promises = nodes.map(function(node){
// trace returns a promise ...
var promise = builder.trace(node)
var recusivePromise = promise.then(function(tree){
// if we had children, get those
if (tree.children.length)
return tracer(tree.children));
else
return node;// the leaf node itself
});
return recusivePromise; // which will resolve with the `tracer(…)` result
// or the leaf
});
return RSVP.all(promises);
}
tracer(myArr).then(function(allTrees){ … });
I ended up going with a counter type approach ...
var traceDeps = function(parents, cb){
var count = 0,
trees = [],
trace = function(nodes){
nodes.forEach(function(node){
count++;
builder.trace(node).then(function(tree){
trees.push(tree);
if(tree.children.length){
trace(tree.children);
}
count--;
if (count === 0) cb(trees);
});
});
};
trace(parents);
};
traceDeps(myArr, function(trees){ ... });
Related
I would like that the for loop will be executed and then the result will be sent over the next function:
FindIdsRequests = function(){
results = $scope.requests
var deferred = $q.defer();
var promise = deferred.promise;
var array = []
for (var i in results) {
promise = promise.then(function(){
array.push(results[i].uid)
})
return promise
}
return promise.then(function(){
return array
})
}
$scope.ConfirmRequests = function(){
//alert('req'+JSON.stringify($scope.requests))
FindIdsRequests().then(function(array){
alert('arr'+JSON.stringify(array))
})
})
the FindIdsRequests function should return the result of the for loop however there is no return (the alert is not printed so does not arrive there). Any idea?
Problems:
You are overwriting promise in each iteration of loop... so there is only ever one promise
There is no resolve of promise to ever trigger then() so the array will always be empty
You have a return in the loop so the loop will break on first iteration and never complete
If this were asynchronous i will not be what you think it is inside then() as it will have reached it's end before the promise resolves
I see no need to write this up in code since everything you are doing is synchronous and shows no need for promise in the first place
You can take advantages of $q insted of returning a promise like so:
$q.all([promise1, promise2]);
For example:
FindIdsRequests = function(){
var requests = $scope.requests;
var results = [];
var array = [];
for (var i in requests) {
var req = $http.get('http://someUrl.com/' + requests[i].uid);
array.push(req);
req.then(function (response) {
results.push(response.data);
});
}
return $q.all(array).then(function(){
return results;
});
}
This code will return a promisse of all promisses in the array to be resolved.
Ok thanks also to the comments of #charlietlf the following code works:
FindIdsRequests = function(){
results = $scope.requests
var deferred = $q.defer();
var promise = deferred.promise;
var array = []
for (var i in results) {
alert('www'+JSON.stringify(results[i].uid))
var obj = results[i].uid
promise = promise.then(function(){
array.push(results[i].uid)
})
//return promise
}
deferred.resolve(array)
return promise.then(function(){
alert('ee'+JSON.stringify(array))
return array
})
}
essentially I forgot to put the resolve.
Creating a Chain of Promises from an Array of Promises
Use $.when to create an empty promise and use a for loop to create a chain:
function chainPromiseArray(promiseArray){
var promise = $q.when();
var array = [];
for (var i=0; i < promiseArray.length; i++) {
promise = promise.then(function(){
var derivedPromise = promiseArray[i].then(function(result) {
//push data
array.push(result.data);
});
//return promise to chain
return derivedPromise;
});
};
var finalPromise = promise.then(function(){
return array;
});
return finalPromise;
};
The above example starts with an empty promise, then chains from that empty promise. The final promise will resolve to the array created by the chain.
Chaining promises
Because calling the .then method of a promise returns a new derived promise, it is easily possible to create a chain of promises. It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. This makes it possible to implement powerful APIs.
--AngularJS $q Service API Reference -- chaining promises
The example executes the promises sequentially. To execute the promises in parallel use the $q.all method.
Your FindIdsRequests() function has no async elements in the code as shown so it can just be a synchronous function and, in fact, trying to use promises in it is just complicating a very simple operation:
// get an array of uid from $scope.requests
function FindIdsRequests() {
return $scope.requests.map(function(item) {
return item.uid;
});
});
$scope.ConfirmRequests = function() {
var ids = FindIdsRequests();
console.log('arr: '+ JSON.stringify(ids));
})
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().
I was trying to use promises to force serialization of a series of Ajax calls. These Ajax calls are made one for each time a user presses a button. I can successfully serialize the operations like this:
// sample async function
// real-world this is an Ajax call
function delay(val) {
log("start: ", val);
return new Promise(function(resolve) {
setTimeout(function() {
log("end: ", val);
resolve();
}, 500);
});
}
// initialize p to a resolved promise
var p = Promise.resolve();
var v = 1;
// each click adds a new task to
// the serially executed queue
$("#run").click(function() {
// How to detect here that there are no other unresolved .then()
// handlers on the current value of p?
p = p.then(function() {
return delay(v++);
});
});
Working demo: http://jsfiddle.net/jfriend00/4hfyahs3/
But, this builds a potentially never ending promise chain since the variable p that stores the last promise is never cleared. Every new operation just chains onto the prior promise. So, I was thinking that for good memory management, I should be able to detect when there are no more .then() handlers left to run on the current value of p and I can then reset the value of p, making sure that any objects that the previous chain of promise handlers might have held in closures will be eligible for garbage collection.
So, I was wondering how I would know in a given .then() handler that there are no more .then() handlers to be called in this chain and thus, I can just do p = Promise.resolve() to reset p and release the previous promise chain rather than just continually adding onto it.
I'm being told that a "good" promise implementation would not cause accumulating memory from an indefinitely growing promise chain. But, there is apparently no standard that requires or describes this (other than good programming practices) and we have lots of newbie Promise implementations out there so I have not yet decided if it's wise to rely on this good behavior.
My years of coding experience suggest that when implementations are new, facts are lacking that all implementations behave a certain way and there's no specification that says they should behave that way, then it might be wise to write your code in as "safe" a way as possible. In fact, it's often less work to just code around an uncertain behavior than it is to go test all relevant implementations to find out how they behave.
In that vein, here's an implementation of my code that seems to be "safe" in this regard. It just saves a local copy of the global last promise variable for each .then() handler and when that .then() handler runs, if the global promise variable still has the same value, then my code has not chained any more items onto it so this must be the currently last .then() handler. It seems to work in this jsFiddle:
// sample async function
// real-world this is an Ajax call
function delay(val) {
log("start: ", val);
return new Promise(function(resolve) {
setTimeout(function() {
log("end: ", val);
resolve();
}, 500);
});
}
// initialize p to a resolved promise
var p = Promise.resolve();
var v = 1;
// each click adds a new task to
// the serially executed queue
$("#run").click(function() {
var origP = p = p.then(function() {
return delay(v++);
}).then(function() {
if (p === origP) {
// no more are chained by my code
log("no more chained - resetting promise head");
// set fresh promise head so no chance of GC leaks
// on prior promises
p = Promise.resolve();
v = 1;
}
// clear promise reference in case this closure is leaked
origP = null;
}, function() {
origP = null;
});
});
… so that I can then reset the value of p, making sure that any objects that the previous chain of promise handlers might have held in closures will be eligible for garbage collection.
No. A promise handler that has been executed (when the promise has settled) is no more needed and implicitly eligible for garbage collection. A resolved promise does not hold onto anything but the resolution value.
You don't need to do "good memory management" for promises (asynchronous values), your promise library does take care of that itself. It has to "release the previous promise chain" automatically, if it doesn't then that's a bug. Your pattern works totally fine as is.
How do you know when the promise chain has completely finished?
I would take a pure, recursive approach for this:
function extendedChain(p, stream, action) {
// chains a new action to p on every stream event
// until the chain ends before the next event comes
// resolves with the result of the chain and the advanced stream
return Promise.race([
p.then(res => ({res}) ), // wrap in object to distinguish from event
stream // a promise that resolves with a .next promise
]).then(({next, res}) =>
next
? extendedChain(p.then(action), next, action) // a stream event happened first
: {res, next:stream}; // the chain fulfilled first
);
}
function rec(stream, action, partDone) {
return stream.then(({next}) =>
extendedChain(action(), next, action).then(({res, next}) => {
partDone(res);
return rec(next, action, partDone);
});
);
}
var v = 1;
rec(getEvents($("#run"), "click"), () => delay(v++), res => {
console.log("all current done, none waiting");
console.log("last result", res);
}); // forever
with a helper function for event streams like
function getEvents(emitter, name) {
var next;
function get() {
return new Promise((res) => {
next = res;
});
}
emitter.on(name, function() {
next({next: get()});
});
return get();
}
(Demo at jsfiddle.net)
It is impossible to detect when no more handlers are added.
It is in fact an undecidable problem. It is not very hard to show a reduction to the halting (or the Atm problem). I can add a formal reduction if you'd like but in handwavey: Given an input program, put a promise at its first line and chain to it at every return or throw - assuming we have a program that solves the problem you describe in this question - apply it to the input problem - we now know if it runs forever or not solving the halting problem. That is, your problem is at least as hard as the halting problem.
You can detect when a promise is "resolved" and update it on new ones.
This is common in "last" or in "flatMap". A good use case is autocomplete search where you only want the latest results. Here is an [implementation by Domenic
(https://github.com/domenic/last):
function last(operation) {
var latestPromise = null; // keep track of the latest
return function () {
// call the operation
var promiseForResult = operation.apply(this, arguments);
// it is now the latest operation, so set it to that.
latestPromise = promiseForResult;
return promiseForResult.then(
function (value) {
// if we are _still_ the last value when it resovled
if (latestPromise === promiseForResult) {
return value; // the operation is done, you can set it to Promise.resolve here
} else {
return pending; // wait for more time
}
},
function (reason) {
if (latestPromise === promiseForResult) { // same as above
throw reason;
} else {
return pending;
}
}
);
};
};
I adapted Domenic's code and documented it for your problem.
You can safely not optimize this
Sane promise implementations do not keep promises which are "up the chain", so setting it to Promise.resolve() will not save memory. If a promise does not do this it is a memory leak and you should file a bug against it.
I tried to check if we can see the promise's state in code, apprantly that is only possible from console, not from code, so I used a flag to moniter the status, not sure if there is a loophole somewhere:
var p
, v = 1
, promiseFulfilled = true;
function addPromise() {
if(!p || promiseFulfilled){
console.log('reseting promise...');
p = Promise.resolve();
}
p = p.then(function() {
promiseFulfilled = false;
return delay(v++);
}).then(function(){
promiseFulfilled = true;
});
}
fiddle demo
You could push the promises onto an array and use Promise.all:
var p = Promise.resolve,
promiseArray = [],
allFinishedPromise;
function cleanup(promise, resolvedValue) {
// You have to do this funkiness to check if more promises
// were pushed since you registered the callback, though.
var wereMorePromisesPushed = allFinishedPromise !== promise;
if (!wereMorePromisesPushed) {
// do cleanup
promiseArray.splice(0, promiseArray.length);
p = Promise.resolve(); // reset promise
}
}
$("#run").click(function() {
p = p.then(function() {
return delay(v++);
});
promiseArray.push(p)
allFinishedPromise = Promise.all(promiseArray);
allFinishedPromise.then(cleanup.bind(null, allFinishedPromise));
});
Alternatively, since you know they are executed sequentially, you could have each completion callback remove that promise from the array and just reset the promise when the array is empty.
var p = Promise.resolve(),
promiseArray = [];
function onPromiseComplete() {
promiseArray.shift();
if (!promiseArray.length) {
p = Promise.resolve();
}
}
$("#run").click(function() {
p = p.then(function() {
onPromiseComplete();
return delay(v++);
});
promiseArray.push(p);
});
Edit: If the array is likely to get very long, though, you should go with the first option b/c shifting the array is O(N).
Edit: As you noted, there's no reason to keep the array around. A counter will work just fine.
var p = Promise.resolve(),
promiseCounter = 0;
function onPromiseComplete() {
promiseCounter--;
if (!promiseCounter) {
p = Promise.resolve();
}
}
$("#run").click(function() {
p = p.then(function() {
onPromiseComplete();
return delay(v++);
});
promiseCounter++;
});
I have a list of objects. The objects are passed to a deferred function. I want to call the function with the next object only after the previous call is resolved. Is there any way I can do this?
angular.forEach(objects, function (object) {
// wait for this to resolve and after that move to next object
doSomething(object);
});
Before ES2017 and async/await (see below for an option in ES2017), you can't use .forEach() if you want to wait for a promise because promises are not blocking. Javascript and promises just don't work that way.
You can chain multiple promises and make the promise infrastructure sequence them.
You can iterate manually and advance the iteration only when the previous promise finishes.
You can use a library like async or Bluebird that will sequence them for you.
There are lots of different alternatives, but .forEach() will not do it for you.
Here's an example of sequencing using chaining of promises with angular promises (assuming objects is an array):
objects.reduce(function(p, val) {
return p.then(function() {
return doSomething(val);
});
}, $q.when(true)).then(function(finalResult) {
// done here
}, function(err) {
// error here
});
And, using standard ES6 promises, this would be:
objects.reduce(function(p, val) {
return p.then(function() {
return doSomething(val);
});
}, Promise.resolve()).then(function(finalResult) {
// done here
}, function(err) {
// error here
});
Here's an example of manually sequencing (assuming objects is an array), though this does not report back completion or errors like the above option does:
function run(objects) {
var cntr = 0;
function next() {
if (cntr < objects.length) {
doSomething(objects[cntr++]).then(next);
}
}
next();
}
ES2017
In ES2017, the async/wait feature does allow you to "wait" for a promise to fulfill before continuing the loop iteration when using non-function based loops such as for or while:
async function someFunc() {
for (object of objects) {
// wait for this to resolve and after that move to next object
let result = await doSomething(object);
}
}
The code has to be contained inside an async function and then you can use await to tell the interpreter to wait for the promise to resolve before continuing the loop. Note, while this appears to be "blocking" type behavior, it is not blocking the event loop. Other events in the event loop can still be processed during the await.
Yes you can use angular.forEach to achieve this.
Here is an example (assuming objects is an array):
// Define the initial promise
var sequence = $q.defer();
sequence.resolve();
sequence = sequence.promise;
angular.forEach(objects, function(val,key){
sequence = sequence.then(function() {
return doSomething(val);
});
});
Here is how this can be done using array.reduce, similar to #friend00's answer (assuming objects is an array):
objects.reduce(function(p, val) {
// The initial promise object
if(p.then === undefined) {
p.resolve();
p = p.promise;
}
return p.then(function() {
return doSomething(val);
});
}, $q.defer());
check $q on angular:
function outerFunction() {
var defer = $q.defer();
var promises = [];
function lastTask(){
writeSome('finish').then( function(){
defer.resolve();
});
}
angular.forEach( $scope.testArray, function(value){
promises.push(writeSome(value));
});
$q.all(promises).then(lastTask);
return defer;
}
The easiest way is to create a function and manually iterate over all the objects in the array after each promise is resolved.
var delayedFORLoop = function (array) {
var defer = $q.defer();
var loop = function (count) {
var item = array[count];
// Example of a promise to wait for
myService.DoCalculation(item).then(function (response) {
}).finally(function () {
// Resolve or continue with loop
if (count === array.length) {
defer.resolve();
} else {
loop(++count);
}
});
}
loop(0); // Start loop
return defer.promise;
}
// To use:
delayedFORLoop(array).then(function(response) {
// Do something
});
Example is also available on my GitHub:
https://github.com/pietervw/Deferred-Angular-FOR-Loop-Example
I use a simple solution for a connection to a printer that wait till the promise is over to go to the next.
angular.forEach(object, function(data){
yourFunction(data)
.then(function (){
return;
})
})
It might help someone as I tried several of above solution before coming up with my own that actually worked for me (the other ones didn't)
var sequence;
objects.forEach(function(item) {
if(sequence === undefined){
sequence = doSomethingThatReturnsAPromise(item)
}else{
sequence = sequence.then(function(){
return doSomethingThatReturnsAPromise(item)
});
}
});
It worked for me like this. I don't know if it is a right approach but could help to reduce lines
function myFun(){
var deffer = $q.defer();
angular.forEach(array,function(a,i) {
Service.method(a.id).then(function(res) {
console.log(res);
if(i == array.length-1) {
deffer.resolve(res);
}
});
});
return deffer.promise;
}
myFun().then(function(res){
//res here
});
So I know that you need to use promises in Angular to manage async, but I'm not quite sure how to do it in this instance.
function getLineGraphData(promises){
var points = [];
for (var i = 0; i < promises.length; i++) {
$http.get('/file/tsDataPoints/'+promises[i].unit+"?startDate="+promises[i].startTs+"&endDate="+promises[i].endTs+"&startState="+promises[i].startState+"&endState="+promises[i].endState).success(function(data){
points.push(data);
console.log(data);
});
}
console.log(points);
return points;
}
I think what is throwing me off here is that the data I'm using to build my queries is in an array. So I don't just need to get the return from one request. There will be several. What do I need to do? Examples would be helpful. Thanks.
$http returns a promise. $q.all takes array of promises and resolves when all of them are resolved. So it's possible to:
promises = promises.map(function(promise){
return $http.get('/file/tsDataPoints/'+promise.unit+"?startDate="+promise.startTs+"&endDate="+promise.endTs+"&startState="+promise.startState+"&endState="+promise.endState);
});
$q.all(promises).then(function(resultsArray){
console.log(resultsArray); // array of results of all your $http's
});
Upd:
And if you need to return it from your function, you can
function getLineGraphData(promises){
promises = promises.map(function(promise){
return $http.get('/file/tsDataPoints/'+promise.unit+"?startDate="+promise.startTs+"&endDate="+promise.endTs+"&startState="+promise.startState+"&endState="+promise.endState);
});
return $q.all(promises);
}
And call it like that
getLineGraphData(promisesArr).then(function(resultsArray){
// do your stuff
});