Do multiple promises on an array of elements [closed] - javascript

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 6 years ago.
Improve this question
I have a list of objectIds and I want to go to different collections and do operations base on each Id. I would prefer doing the operation one after the other (sequentially)
var removeOperation = function(objectified){
return Comps.findOne({reviews : objectified}).populate([{ path: "reviews", match : {_id : objectified}}])
}
var firstCheckIfAnonHasTheIdInReviewsArrayIfThereDeleteIt = function(objectified){
var query = {reviews : objectified};
var update = {$pull : {reviews : objectified}};
var option = {new :true};
return Anon.findOneAndUpdate(query, update, option );
};
var thenCheckIfUserHasTheIdInReviewsArrayIfThereDeleteIt = function(objectified){
var query = {reviews : objectified};
var update = {$pull : {reviews : objectified}};
var option = {new :true};
return User.findOneAndUpdate(query, update, option );
}
I was going down this path:
Promise.mapSeries(arrOfObjectIds, function(e){
return removeOperation(e);
})
.then(function(results){
console.log(results);
var map = results.map(function(e){
// return e.reviews[0]
return e
})
console.log("map : ", map)
return Promise.resolve(map);
})
.then(function(compDocs){
console.log("compDocs: ",compDocs)
Promise.mapSeries(compDocs, function(compDoc){
return updateCompAndRemoveReviewFromArray(compDoc) // I know it's not show. It's another promise I use
})
}).then(function(returned){
return Reviews.remove({_id : {$in : arrOfObjectIds }})
})
.then(function(){
I wanted to do firstCheckIfAnonHasTheIdInReviewsArrayIfThereDeleteIt on the array of object Ids to delete the review from the array. Also if we succesfully removed the array here we should not have to go to the next user
promise which deletes a users review since if we deleted in Anon it won't be in User. since there is only one review ID possible per review.
})
.then(function(){
//if there was no review pulled from the Anon reviews Array. that means it's in the users review and we should do this promise
thenCheckIfUserHasTheIdInReviewsArrayIfThereDeleteIt()
})
So maybe you can show me how to use mapSeries on an array of elements so that it just doesn't d one promise but does multiple promises.
can we doe something like:
Promise.mapSeries(arrOfObjectIds, function(e){
return removeOperation(e);
return firstCheckIfAnonHasTheIdInReviewsArrayIfThereDeleteIt(e)// extra credit: check if this was successful (review was pulled). If it wasn't got to next one.
return thenCheckIfUserHasTheIdInReviewsArrayIfThereDeleteIt(e)
})

Restating the problem in simplified terms :
You have an array of IDs and for each ID in turn you want to call three promise-returning functions, A, B and C as follows :
A(id) (unconditionally)
then B(id) (unconditionally)
then C(id) (conditionally, depending on the outcome of B(id))
Can we do something like:
Promise.mapSeries(arrOfObjectIds, function(e){
return removeOperation(e);
return firstCheckIfAnonHasTheIdInReviewsArrayIfThereDeleteIt(e)// extra credit: check if this was successful (review was pulled). If it wasn't got to next one.
return thenCheckIfUserHasTheIdInReviewsArrayIfThereDeleteIt(e)
})
Yes, though not very like the suggested code.
First, you have a design choice concerning the way in which B reports its outcome. The question hints at B's outcome being a case of "success" vs "failure", but that's not the only way to model it.
Option 1: Test data delivered down the promise chain's success path
Write B such that its returned promise will fulfill both on success (Anon review was deleted) or on the expected failure (Anon review was not deleted), and report the outcome by means of a parameter.
var B = function(objectified) {
var query = {reviews: objectified};
var update = {$pull: {reviews: objectified}};
var option = {new :true};
return Anon.findOneAndUpdate(query, update, option).exec();
};
Then you would write :
Promise.mapSeries(arrOfObjectIds, function(id) {
return A(id).then(function() {
return B(id);
}).then(function(item) { // item will be `null` if B(id) found nothing.
return item || C(id);
}).catch(function(error) {
// If anything went wrong, catch the error and log it.
console.log(error);
// By not re-throwing the error, the mapseries() is allowed to continue.
});
});
Option 2: Test error delivered down the promise chain's failure path
Write B such that its returned promise will fulfill on success, or reject on expected failure.
var B = function(objectified) {
var query = {reviews: objectified};
var update = {$pull: {reviews: objectified}};
var option = {new :true};
return Anon.findOneAndUpdate(query, update, option).exec().then(function(item) {
return item || Promise.reject(new Error('not found'));
});
};
Then you would write :
Promise.mapSeries(arrOfObjectIds, function(id) {
return A(id).then(function() {
return B(id).catch(function(error) {
// Here, you have to discriminate between the "expected error" and any unexpected errors.
if(error.message === 'not found') {
return C(id);
} else {
throw error; // unexpected error - rethrow it
}
});
}).catch(function(error) {
// If anything went wrong, catch the error and log it.
console.log(error);
// By not re-throwing the error, the overall mapseries() is allowed to continue.
});
});
In both options :
to return a genuine Promise, use .exec() in A, B and C. (As I understand Mongoose, without exec() you get something which has a .then() method, but which is not a full-blown Promise).
if you want the overall sequence to stop on first error, then rethrow the error after logging it, or completely omit the final catch().
further unconditional stages can be added very simply, before or after the conditional stage.
For me, Option 2 is more logical, though I would probably choose Option 1 for its greater simplicity and efficiency.

You can use Array.reduce() to execute your promises in series:
arrOfObjectIds.reduce(function(promise, objectId) {
return promise.then(function(result) {
return removeOperation(objectId)
.then(firstCheckIfAnonHasTheIdInReviewsArrayIfThereDeleteIt)
.then(thenCheckIfUserHasTheIdInReviewsArrayIfThereDeleteIt);
});
}, Promise.resolve());
This will perform the chain of removeOperation -> firstCheck.. -> thenCheck one item in the array at a time, then move to the next item.

can we doe something like: yes, like that, except the first return exits the function
so, you could possibly do something like
Promise.mapSeries(arrOfObjectIds, function(e){
return removeOperation(e)
.then(function() {
return firstCheckIfAnonHasTheIdInReviewsArrayIfThereDeleteIt(e);
}).then(function() {
return thenCheckIfUserHasTheIdInReviewsArrayIfThereDeleteIt(e);
})
})

Related

Awaiting code until filter is finished - Nextjs/Javascript

I am looking on how to make my code after my filter function await the results of my filter function to complete before running. However I am not sure how to do this.
My filter function takes in another function (useLocalCompare) which causes the execution of my filter function to be a little longer than normal, which then leads to my next piece of code (that depends on the results of my filter function) executing before my filter function is complete.....which leads to undefined.
Is there anything similar to a callback I can use to force my subsequent piece of code to wait till the filter is finished?
Relevant code is written below.
if (flatarrayofvalues !== null && genre !== null) {
const filtteredarray = await flatarrayofvalues.filter(
(placeholder) => {
if (useLocalCompare(genre, placeholder.name) == true) {
console.log("HURAY!!!!", placeholder.id, placeholder.name);
placeholder.name == placeholder.name;
}
}
);
console.log("MY FILTERED ARRAY IS", filtteredarray);
console.log("The ID FOR MY MY FILERED ARRAY IS two ID", filtteredarray[0]?.id);
return filtteredarray[0].id;
}
}
}
For those curious, useLocalCompare basically checks to see if the genre parameter pulled down from the URL is the same as a name parameter from the array I am filtering. Reason I have this is due to people having different case sensitivity when putting in URLS. EX: it will pull down "HORrOR" and match it to the object name in the array I am filtering called "horror". I then extract the ID from that object.
you have to return the conditional from filter as it is "explicit return"
const filtteredarray = await flatarrayofvalues.filter(
(placeholder) => {
if (useLocalCompare(genre, placeholder.name) == true) {
console.log("HURAY!!!!", placeholder.id, placeholder.name);
return placeholder.name == placeholder.name; // here
// why not just return true ?? instead of above line
}return false
}
);
Also I'm not sure this makes sense
placeholder.name == placeholder.name; you mean just return true; ?

Angular & rxjs catch error when find returns undefined

I am trying to figure out how to catch an error when the variable selectionId that I define from my route is either null, or an invalid value. Currently, if my category variable is undefined, it is caught by the rxjs function catchError, and throws to my error subscription block appropriately.
But when selectionId is undefined, it still goes into the success block, where the successful result is undefined.
loadSelection() {
// define the menu category chosen (e.g. 'pizza')
const category = this.route.snapshot.url[0].path;
// define which item in the menu was selected (e.g. 1)
const selectionId = this.route.snapshot.url[1].path;
// in the menu state at the given category, find the menu item with the matching selectionId
this.store.select('menu')
.pipe(map(menuState => menuState.menu.groupedMenu[category]
.find(selection => selection._id === selectionId)),
catchError(err => throwError('given category does not exist'))
).subscribe(
(selection: Selection) => { // success case
this.selection = selection;
},
(err) => { // error case
console.log('weow', err);
});
}
What is the best way to handle errors when my .find returns undefined?
I could just put an if statement checking for undefined in my success block, but I don't feel like this is clean code. But maybe I'm wrong.
Thank you for any help
I realize that you are specifically asking how to handle the error in the rx stream,
but since you have both category and selectionId before you even do the select,
why not just check and handle the error condition before the select?

firebase transaction and if-then-else behaviour

I have some changes in my requirements:
Not only Create/Request/Cancel an entire Offer but do some actions on Offer's details:
Here is an offer in the activeOffers list:
activeOffers
-LKohyZ58cnzn0vCnt9p
details
direction: "city"
seatsCount: 2
timeToGo: 5
uid: "-ABSIFJ0vCnt9p8387a" ---- offering user
A user should be able to 'ask for seats' and if it's successful the Offer record should look like this:
activeOffers
-LKohyZ58cnzn0vCnt9p
details
direction: "city"
seatsCount: 1 ----- reduced count
timeToGo: 5
uid: "-ABSIFJ0vCnt9p8387a"
deals
-GHFFJ0vCnt9p8345b ----- the userId of asking user
seatsCount: 1
status: "asked"
But I have 3 problems after executing the source shown below:
(as shown above offer has 2 seats and a user asks for 1 seat)
After execution in my log I have BOTH "Reducing seats count by 1" and "Not enought seats"... i.e: the 'then' and 'else' part of 'if-then-else' :o
function result is [] - i.e. no deal created.
I'm not sure how to do the TODO: part - to add child (the new deal object) under dealsRef using asking userId as KEY because I think I don't need an autogenerated key here.
input data has the following structure:
data
"uid": "-GHFFJ0vCnt9p8345b", ----- the userId of asking user
"id": "-LKohyZ58cnzn0vCnt9p", ----- the id of offer
"details":
"seatsCount": 1
And here is my code:
dealSeats = function(data) {
const TAG = '[dealSeats]: ';
var details = data.details;
var info = data.info;
var entryRef = db.ref('activeOffers/' + data.id);
var entryDetailsRef = entryRef.child('details');
var seatsCountRef = entryDetailsRef.child('seatsCount');
var success = false;
return seatsCountRef.transaction((current)=>{
var value = current;
if (value >= details.seatsCount) {
success = true;
value = value - details.seatsCount;
console.log(TAG + 'Reducing seats count by ' + details.seatsCount);
} else {
console.log(TAG + 'Not enought seats');
}
return value;
})
.then(()=>{
var deal = [];
if (success) {
console.log(TAG + 'Succes');
deal.seatsCount = details.seatsCount;
deal.status = 'asked';
// TODO: here should add the new deal to dealsRef
return deal;
} else {
console.log(TAG + 'Failure');
return deal;
}
})
}
And as you can see - I'm not sure what is the right way to check if transaction is succeeded...
The reference documentation for DatabaseReference.transaction says:
... until your write succeeds without conflict or you abort the transaction by not returning a value from your update function.
So the way to abort the transaction is by not returning any value from your update function. That means the entire first block can be simplified to:
seatsCountRef.transaction((current)=>{
if (current >= details.seatsCount) {
return value - details.seatsCount;
}
})
Now it either returns the new value, or it returns nothing. The latter will then make Firebase abort the transaction.
To detect the final output of a transaction, I find it easiest to work with a completion callback (instead of a Promise), since it gives you all parameters in one call:
seatsCountRef.transaction((current)=>{
if (current >= details.seatsCount) {
return value - details.seatsCount;
}
}, function(error, committed, snapshot) {
if (error) {
console.log('Transaction failed abnormally!', error);
} else if (!committed) {
console.log('We aborted the transaction, because there are not enough seats.');
} else {
console.log('Seat count updated');
}
})
The most common cause for that first error condition will be that the transaction had to be retried too frequently, meaning that too many users are trying to claim seats at the same time. A typical solution here would be to back off, i.e. have the client retry later.

Passing values from one promise to the next [duplicate]

This question already has answers here:
How do I access previous promise results in a .then() chain?
(17 answers)
Closed 5 years ago.
A bit new to javascript.
Been dealing with promises, however ran into a problem i dont know how to approach.
How can i pass a value into the next promise resolve?
Here's my code
bot.on('ask.add_account_password', (msg) => {
let username = users[msg.from.id].username;
let password = msg.text;
var accounts = require('./inc/account.js');
accounts.login_account(username,password).then(function(data){
var account = data.params;
console.log(account);
return accounts.store(msg.from.id,username,password,account.followerCount);
}).then(function(data){
let caption = "Your account "+account.username+"("+account.fullName+")has been added\n";
return bot.sendPhoto(msg.from.id, account.picture, {caption:caption});
})
.catch(function(error){
console.log(error);
add_account(msg,error.name);
});
});
On the line where i create the caption variable, i'm trying to access the account object created in the block before it(var account = data.params) but i get a reference error saying its not defined. Now i can easily bypass this by just sending the entire object into the accounts.store function and have it resolve the object when done, but that just seems like a dirty workaround to a bigger problem. Is there a cleaner way to do this?
You can create variable (and set it in promise) in main function or you can return this as result of first promise instead of result from function store
account is undefined at second .then(), use data to reference the Promise value accounts.store returned from previous .then()
.then(function(data) {
// `data` : `accounts.store` returned from previous `.then()`
let caption = "Your account " + data.username
+ "(" + data.fullName + ")has been added\n";
return bot.sendPhoto(msg.from.id, data.picture, {caption:caption});
})
You can pass it in an array and use destructuring in the arguments to unpack the array.
accounts.login_account(username,password).then(function(data){
var account = data.params;
console.log(account);
return [account, accounts.store(msg.from.id,username,password,account.followerCount)];
}).then(function([account, data]){
let caption = "Your account "+account.username+"("+account.fullName+")has been added\n";
return bot.sendPhoto(msg.from.id, account.picture, {caption:caption});
})
You don't actually appear to be using data, so you could just return account after calling store

forcing completion of an rxjs observer

I've got an rxjs observer (really a Subject) that tails a file forever, just like tail -f. It's awesome for monitoring logfiles, for example.
This "forever" behavior is great for my application, but terrible for testing. Currently my application works but my tests hang forever.
I'd like to force an observer change to complete early, because my test code knows how many lines should be in the file. How do I do this?
I tried calling onCompleted on the Subject handle I returned but at that point it's basically cast as an observer and you can't force it to close, the error is:
Object # has no method 'onCompleted'
Here's the source code:
function ObserveTail(filename) {
source = new Rx.Subject();
if (fs.existsSync(filename) == false) {
console.error("file doesn't exist: " + filename);
}
var lineSep = /[\r]{0,1}\n/;
tail = new Tail(filename, lineSep, {}, true);
tail.on("line", function(line) {
source.onNext(line);
});
tail.on('close', function(data) {
console.log("tail closed");
source.onCompleted();
});
tail.on('error', function(error) {
console.error(error);
});
this.source = source;
}
And here's the test code that can't figure out how to force forever to end (tape style test). Note the "ILLEGAL" line:
test('tailing a file works correctly', function(tid) {
var lines = 8;
var i = 0;
var filename = 'tape/tail.json';
var handle = new ObserveTail(filename);
touch(filename);
handle.source
.filter(function (x) {
try {
JSON.parse(x);
return true;
} catch (error) {
tid.pass("correctly caught illegal JSON");
return false;
}
})
.map(function(x) { return JSON.parse(x) })
.map(function(j) { return j.name })
.timeout(10000, "observer timed out")
.subscribe (
function(name) {
tid.equal(name, "AssetMgr", "verified name field is AssetMgr");
i++;
if (i >= lines) {
handle.onCompleted(); // XXX ILLEGAL
}
},
function(err) {
console.error(err)
tid.fail("err leaked through to subscriber");
},
function() {
tid.end();
console.log("Completed");
}
);
})
It sounds like you solved your problem, but to your original question
I'd like to force an observer change to complete early, because my test code knows how many lines should be in the file. How do I do this?
In general the use of Subjects is discouraged when you have better alternatives, since they tend to be a crutch for people to use programming styles they are familiar with. Instead of trying to use a Subject I would suggest that you think about what each event would mean in an Observable life cycles.
Wrap Event Emitters
There already exists wrapper for the EventEmitter#on/off pattern in the form of Observable.fromEvent. It handles clean up and keeping the subscription alive only when there are listeners. Thus ObserveTail can be refactored into
function ObserveTail(filename) {
return Rx.Observable.create(function(observer) {
var lineSep = /[\r]{0,1}\n/;
tail = new Tail(filename, lineSep, {}, true);
var line = Rx.Observable.fromEvent(tail, "line");
var close = Rx.Observable.fromEvent(tail, "close");
var error = Rx.Observable.fromEvent(tail, "error")
.flatMap(function(err) { return Rx.Observable.throw(err); });
//Only take events until close occurs and wrap in the error for good measure
//The latter two are terminal events in this case.
return line.takeUntil(close).merge(error).subscribe(observer);
});
}
Which has several benefits over the vanilla use of Subjects, one, you will now actually see the error downstream, and two, this will handle clean up of your events when you are done with them.
Avoid *Sync Methods
Then this can be rolled into your file existence checking without the use of readSync
//If it doesn't exist then we are done here
//You could also throw from the filter if you want an error tracked
var source = Rx.Observable.fromNodeCallback(fs.exists)(filename)
.filter(function(exists) { return exists; })
.flatMap(ObserveTail(filename));
Next you can simplify your filter/map/map sequence down by using flatMap instead.
var result = source.flatMap(function(x) {
try {
return Rx.Observable.just(JSON.parse(x));
} catch (e) {
return Rx.Observable.empty();
}
},
//This allows you to map the result of the parsed value
function(x, json) {
return json.name;
})
.timeout(10000, "observer timed out");
Don't signal, unsubscribe
How do you stop "signal" a stop when streams only travel in one direction. We rarely actually want to have an Observer directly communicate with an Observable, so a better pattern is to not actually "signal" a stop but to simply unsubscribe from the Observable and leave it up to the Observable's behavior to determine what it should do from there.
Essentially your Observer really shouldn't care about your Observable more than to say "I'm done here".
To do that you need to declare a condition you want to reach in when stopping.
In this case since you are simply stopping after a set number in your test case you can use take to unsubscribe. Thus the final subscribe block would look like:
result
//After lines is reached this will complete.
.take(lines)
.subscribe (
function(name) {
tid.equal(name, "AssetMgr", "verified name field is AssetMgr");
},
function(err) {
console.error(err)
tid.fail("err leaked through to subscriber");
},
function() {
tid.end();
console.log("Completed");
}
);
Edit 1
As pointed out in the comments, In the case of this particular api there isn't a real "close" event since Tail is essentially an infinite operation. In this sense it is no different from a mouse event handler, we will stop sending events when people stop listening. So your block would probably end up looking like:
function ObserveTail(filename) {
return Rx.Observable.create(function(observer) {
var lineSep = /[\r]{0,1}\n/;
tail = new Tail(filename, lineSep, {}, true);
var line = Rx.Observable.fromEvent(tail, "line");
var error = Rx.Observable.fromEvent(tail, "error")
.flatMap(function(err) { return Rx.Observable.throw(err); });
//Only take events until close occurs and wrap in the error for good measure
//The latter two are terminal events in this case.
return line
.finally(function() { tail.unwatch(); })
.merge(error).subscribe(observer);
}).share();
}
The addition of the finally and the share operators creates an object which will attach to the tail when a new subscriber arrives and will remain attached as long as there is at least one subscriber still listening. Once all the subscribers are done however we can safely unwatch the tail.

Categories