I'd like to perform an asynch function conditionally, but I think I'm missing the correct syntax to say what I want.
this.doUpToThreeThings = function(skipTheMiddleStep) {
return doFirstThing().then(function (result) {
if (skipTheMiddleStep) {
return doThirdThing();
} else {
return doSecondThing();
}
}).then(function (result) {
if (skipTheMiddleStep) {
return "ok"; // return what?
} else {
return doThirdThing();
}
});
}
By the time we get to the second then, I don't know if the first block did the middle step, so I'm forced into repeating the condition. And the second block reads weird: it should say, if skip the middle step, then do the third thing, but since it we know that the previous block must have done the third thing, it just returns. So I have to repeat the condition and write pretty wrong-looking code in the second then.
I realize I can write a function called doSecondAndThirdThings, and just call that from the condition in the first block, but that's not really DRY, it's just hiding the non-DRYness. (or maybe I'm wrong about that?)
Also, I'm still a little confused about returning a completed promise on that "ok" branch. Is that right how it is, or should I say something like resolve? -- Thanks
The deferred in thefourtheye's answer is pointless and is considered an anti pattern with promises.
Here is how I would do it:
this.doUpToThreeThings = function(skipTheMiddleStep) {
return doFirstThing().then(function (result) {
return (skipTheMiddleStep) ? doThirdThing() : doSecondThing().then(doThirdThing);
});
}
Related
I created this service method in Angular JS which checks if an array of potential statuses(pendingApplications) match any of an array of set statuses(applicableStatuses). For this to work it meetsStatusCondition should return true after the first match occurs. Only 1 of the numbers in pendingApplications array needs to match and I'd like to end the execution of this function. Currently it's looping through every item in pendingApplications array
`containsApplicableStatus: function(pendingApplications, applicableStatuses) {
pendingApplications.forEach(function(status) {
if (applicableStatuses.includes(status)) {
return pendingApplications.meetsStatusCondition = true;
}
});
}`
This is a limitation with .forEach, you can't break out if it like you can with a for loop
Just a regular for loop will work
for (const status of applicableStatuses){
if (applicableStatuses.includes(status)) {
pendingApplications.meetsStatusCondition = true;
break //or return if you want to exit out of the enclosing function instead of just the loop
}
}
Often when you want to short-circuit a forEach like this, what you're really looking for is another method like find() or some().
containsApplicableStatus: function(pendingApplications, applicableStatuses) {
pendingApplications.meetsStatusCondition = pendingApplications.some(function(status) {
return applicableStatuses.includes(status)
});
}
There is no point in using forEach (which doesn't have a breaking option) if you could just use a regular for ... of loop instead:
containsApplicableStatus: function(pendingApplications, applicableStatuses) {
for (const status of pendingApplications) {
if (applicableStatuses.includes(status)) {
pendingApplications.meetsStatusCondition = true;
break;
}
}
}
However, even this seems a bit too complicated, you could just set meetsStatusCondition to the result of some:
containsApplicableStatus: function(pendingApplications, applicableStatuses) {
pendingApplications.meetsStatusCondition =
pendingApplications.some(status => applicableStatues.includes(status));
}
I do wonder if it makes sense to set a non-index property on your array though, maybe rethink that. This works but it's usually not something you'd expect on an array, and it will be lost if you convert that array to JSON for instance.
How can I achieve a loop like this:
foobar.each(function (model, j) {
// asynchrounous call etc. {in here bool get set to true}
// outside all asynchronous calls
// wait till bool is true, without stopping anything else except the loop to the top of
the _.each
})
I asked a similar question yesterday. But it got marked as a duplicate when it wasn't the same case. Their solution did not achieve the same thing. Also generator functions were suggested which looked like it would work. But I can't use them with ecmascript 5
I've tried busy loops and set time out but they don't seem to work either
I've also tried this:
goto();
function goto() {
if (foo === true) {
//return true; /*I've tried with and without the return because the loops
doesn't need a return*/
} else {
goto();
}
}
What happens with the goto() method is it breaks. Giving me the right results for the first iterations then execution seems to stop altogether. 'foo' always gets set to true in normal execution though.
What you could do is implement a foreach yourself, where you execute your condition, and then on success callback go to the next item (but meanwhile the rest of the code will keep running.
var iteration = 0 //count the iteration of your asynchronous process
//start looping
loop(iteration)
function loop(iteration){
var model = foobar[iteration];
//exit your loop when all iterations have finished (assuming all foobar items are not undefined)
if (foobar[iteration] === undefined){
return;
}
//do what you want
//on success callback
iteration++;
loop(iteration);
//end success callback
}
Suppose that I have a telephony application. I have a feature that I want to try calling an array of users one by one and break the sequence whenever one of the users accepts call, or when the complete operation is cancelled.
I will try to simplify it like this in pseudocode:
for(user in users) {
result = callUserCommand(user);
if(result == "accepted" || result == "cancelled") {
break;
}
}
Here, the callUserCommand is a RACCommand that needs to be async. And it can actually have three return values: "accepted", "cancelled", "declined".
Accepted and Cancelled will break the sequence of operations and won't execute the rest.
Declined, should continue with the execution of the rest of the sequence.
I tried with something like the following, but really couldn't accomplish exactly the thing I described above.
RACSignal *signal = [RACSignal concat:[users.rac_sequence map:^(User * user) {
return [self.callUserCommand execute:user];
}]];
[signal subscribeNext:^(id x) {
} error:^(NSError *error) {
} completed:^{
}];
If I understood correctly you would like to execute the sequence one by one until one of the call gets accepted or cancelled.
Maybe you could give takeUntil or takeWhile a try. I would write this scenario with RAC like this:
NSArray* users = #[#"decline", #"decline", #"decline", #"accept", #"decline"];
[[[[[users.rac_sequence signal]
flattenMap:^RACStream *(NSString* userAction) {
NSLog(#"Calling user (who will %#):", userAction);
// return async call signal here
return [RACSignal return:userAction];
}]
takeWhileBlock:^BOOL(NSString* resultOfCall) {
return [resultOfCall isEqualToString:#"decline"];
}]
doCompleted:^{
NSLog(#"Terminated");
}]
subscribeNext:^(NSString* userAction) {
NSLog(#"User action: %#", userAction);
}];
In the sample code above the last user who would decline the call won't be called.
I am trying to call urls one AFTER another, not all at the same time, but no matter what I seem to try, they seem to all happen at the same time. This is what I have now...
$http.get('/some/url/foo1')
.then($http.get('/some/url/foo2'))
.then($http.get('/some/url/foo3'))
.then($http.get('/some/url/foo4'))
.then($http.get('/some/url/foo5'))
.then($http.get('/some/url/foo6'))
.then($http.get('/some/url/foo7'))
.then($http.get('/some/url/foo8'))
.then($http.get('/some/url/foo9'))
.then($http.get('/some/url/foo10'))
.then($http.get('/some/url/foo11'))
.then($http.get('/some/url/foo12'))
.then($http.get('/some/url/foo13'))
.then($http.get('/some/url/foo14'));
These can't happen at the same time, when one completes, I want the next one to start.
EDIT: I've also tried putting the get in a function like this, but they still all get called at the same time
$http.get('/some/url/foo1')
.then(rebuildModel('foo2'))
.then(rebuildModel('foo3'))
.then(rebuildModel('foo4'))
.then(rebuildModel('foo5'))
.then(rebuildModel('foo6'))
.then(rebuildModel('foo7'))
.then(rebuildModel('foo8'))
.then(rebuildModel('foo9'))
.then(rebuildModel('foo10'))
.then(rebuildModel('foo11'))
.then(rebuildModel('foo12'))
.then(rebuildModel('foo13'))
.then(rebuildModel('foo14'));
function rebuildModel(modelName) {
return $http.get('/some/url/' + modelName);
}
Edit2: This worked...I see what I did wrong
function rebuildModel(modelName) {
return function () {
return $http.get('/some/url/' + modelName);
}
}
The then method expects a success callback function as its first parameter:
$http.get('url').then(successCallback);
The successCallback must be a function definition, such as:
$http.get('url').then(function() { ... });
If you provide $http.get() as an argument:
$http.get('url').then($http.get('url'));
You are calling a function, and then passing the return value (which is a promise object) to the then method as the successCallback.
The situation is analogous to the following more obvious scenario:
a. alert
b. alert()
The first is a function definition, the second is calling the function.
I agree with Chandermani's answer, and it should be correct. If it is not working, perhaps check for errors:
$http.get('/some/url/foo1')
.then(function() { return $http.get('/some/url/foo2'); })
.then(function() { return $http.get('/some/url/foo3');},
function() { alert('something went wrong');});
The chaining that you have done is incorrect. It should be something like this:
$http.get('/some/url/foo1')
.then(function() { return $http.get('/some/url/foo2'); })
.then(function() { return $http.get('/some/url/foo3');})
Remember the then function too returns a promise, that is resolved by the return value of its, success and error callbacks.
I want to get all names of files and directories from path and recognize them as files and directories. but When i run my code sometimes it works and somentimes it shows that directories are files. Here is the code
socket.on('data',function(path){
fs.readdir('path',function(err, data) {
var filestatus=[];
var z=0;
var i=data.length-1;
data.forEach(function(file){
fs.stat(file, function(err, stats) {
filestatus[z]=stats.isDirectory()
if (z==i){
socket.emit('backinfo',{names:data,status:filestatus});
}
z++;
})
})
})
})
During tests i realized that when i slow down data.forEach loop (using console.log(something) it works better(less miss). And this is strange.
This is about 96% incorrect, thank you to JohnnyHK for pointing out my mistake, see the comments below for the real problem / solution.
Because the fs.stat() function call is asynchronous, the operations on the filestatus array are overlapping. You should either use the async library as elmigranto suggested, or switch to using fs.statSync.
More details on what's happening:
When you call fs.stat(), it basically runs in the background and then immediately goes onto the next line of code. When it has got the details of the file, it then calls the callback function, which in your code is the function where you add the information to the filestatus array.
Because fs.stat() doesn't wait before returning, your program is going through the data array very quickly, and mutliple callbacks are being run simultanously and causing issues because the z variable isn't being incremented straight away, so
filestatus[z]=stats.isDirectory()
could be executed multiple times by different callbacks before z gets incremented.
Hope that makes sense!
you are using for statement in NODEJS and this will work if turned the For Statement to recursive function please see the attached code for help
function load_Files(pat,callback) {
console.log("Searching Path is: "+ph);
fs.readdir(pat,(err,files)=>
{
if(err)
{
callback(err);
}
else
{
var onlydir=[];
var onlyfiles=[];
var d=(index)=>
{
if (index==files.length)
{
console.log("last index: "+ index);
var ar=[];
ar.concat(onlydir,onlyfiles);
callback(null,ar);
return;
}
fs.stat(files[index],(err,status)=>
{
console.log("the needed file " +files[index]);
if (status.isDirectory())
{
onlydir.push(files[index]);
}
else
{
onlyfiles.push(files[index]);
}
console.log("only Directory: "+onlydir.length);
console.log("index: "+ index);
d(index+1);
}
)
}
d(0);
}
});
}