I have a component that fetches content from a service to process it. The thing is I can have multiple calls to this function, which results in duplicates on my array. I the following workaround:
getOptions(): Observable<PickQuality[]> {
console.log("MY LENGTH: ", this.options.length) // <=== Always returns 0 because the callback hasn't run yet
if(this.options.length == 0) {
this.championService.getChampions()
.subscribe(champions => {
champions.forEach(champion => this.options.push(new PickQuality(champion, 0)));
this.reevaluate();
this.optionsSubject.next(this.options);
});
return this.optionsSubject.asObservable();
}
else
return Observable.of(this.options);
}
and it didn't work, and then I tried the following trick inside the callback (where the this.options.length is correctly recognized):
if(this.options.length != 0) return; // <=== Looks bad!
which actually worked but seemed extremely inefficient to me, since the call to my service is still executed. How can I fix this?
I'd recommend to restructure your code a little:
if (this.options.length == 0) {
let source = this.championService.getChampions()
.share();
source.subscribe(champions => {
// ... whatever
this.options = whateverResult;
});
return source;
} else {
return Observable.of(this.options);
}
Now you can avoid using Subjects and return the source Observable which represents the HTTP request and is shared via the share() operator. This means there's only one HTTP request and its result is sent to this internal subscribe() call as well as to the subscriber outside this method.
Check for duplicates before pushing them.
this.championService.getChampions()
.subscribe(champions => {
champions.forEach(champion => {
if (champions.indexOf(champion) == -1)
this.options.push(new PickQuality(champion, 0));
});
this.reevaluate();
this.optionsSubject.next(this.options);
});
Related
I'm fairly new with rxjs. I'm calling a function below and the complete stream is read and the read console statements are printed, but I never see a "Subscibe done" and I don't know why. What will it take to get this stream to finish? Is something obviously wrong?
const readline$ = RxNode.fromReadLineStream(rl)
.filter((element, index, observable) => {
if (index >= range.start && index < range.stop) {
console.log(`kept line is ${JSON.stringify(element)}`);
return true;
} else {
console.log(`not keeping line ${JSON.stringify(element)}`);
return false;
}
})
.concatMap(line => Rx.Observable.fromPromise(myFunction(line)))
.do(response => console.log(JSON.stringify(response)));
readline$.subscribe(i => { console.log(`Subscribe object: ${util.inspect(i)}`); },
err => { console.error(`Subscribe error: ${util.inspect(err)}`); },
done => { console.log("Subscribe done."); // NEVER CALLED
anotherFunc(); // NEVER CALLED
}
);
You can see from the source code that it send complete notification only when the source stream emits close event. https://github.com/Reactive-Extensions/rx-node/blob/master/index.js#L100-L102
So if you need the proper complete handler to be called you'll need to close the stream yourself, see How to close a readable stream (before end)?.
In other words the Observable doesn't complete automatically after reading the entire file.
Before I start, let me say that I'm new to Javascript and very new to axios API calls, so I'm probably making a rookie mistake...
I have this function getObjects() that's meant to map over an array and return the data from an Axios API call. The API call and map function are both working, but I'm getting a Promise object instead of the data I'm trying to get.
I figure this is because the data is returned before there's enough time to actually get it, but not sure how to fix? I tried a .setTimeout(), but that didn't seem to work.
getObjects() {
let newsItems = this.state.arrayofids.map((index) => {
let resultOfIndex = axios.get(`https:\/\/hacker-news.firebaseio.com/v0/item/${index}.json`).then((res) => {
let data = res.data;
//console.log(data.by); // this prints the correct byline, but
// all the bylines are printed after
// the console.log below...
if (!data.hasOwnProperty('text')) return data;
}); /// end of axios request
return resultOfIndex;
}); /// end of map
/// ideally this would end in an array of response objects but instead i'm getting an array of promises...
console.log(newsItems);
}
(The extra escape characters are for my text editor's benefit.)
Here's a link to a codepen with the issue - open up the console to see the problem. It's a React project but I don't think any of the React stuff is the issue. EDIT: Codepen is link to working solution using axios.all as suggested below
Thanks!
EDIT: Here is my working solution.
getObjects() {
let axiosArr = [];
axios.all(this.state.arrayofids.map((id) => {
return axios.get(`https:\/\/hacker-news.firebaseio.com/v0/item/${id}.json`)
})).then((res) => {
for (let i = 0; i < this.state.arrayofids.length; i++) {
axiosArr.push(<li key={i} data-url={res[i].data.url} onClick={(e) => this.displayTheNews(e)}>{res[i].data.title}</li>);
}
if (axiosArr.length == this.state.arrayofids.length) {
this.setState({arrayofdata: axiosArr});
console.log('state is set!');
}
})
}
axios.all function should be more appropriate to your current scenario.
Your console.log is executing immediately, rather than waiting for the requests to finish, because they are not synchronous. You have to wait for all the responses before you console.log.
OPTION 1 (the hard way):
replace your console.log with
newsItems.forEach((promise, index) => {
promise.then((object)=>{
newsItems[index] = object
if (index+1 == newsItems.length) {
console.log(newsItems)
}
})
})
OPTION 2 (the better way):
using axios.all
getObjects() {
axios.all(this.state.arrayofids.map((id) => {
return axios.get(`https:\/\/hacker-news.firebaseio.com/v0/item/${id}.json`)
})).then((res) => {
console.log(res)
})
}
by the way, I would definitely reccommend changing
this.state.arrayofids.map((index) => {
let resultOfIndex = axios.get(`https:\/\/hacker-news.firebaseio.com/v0/item/${index}.json`)...
to be called id instead of index
I am trying to use Mongoose's built in promise support to write some clean Javascript code for a user sending a friend request to another. However, when I try to ensure proper error handling and sequentiality, I still end up with a (slightly smaller than normal) pyramid of doom.
Here, I first ensure that the friend request is valid, then save the target's Id to the requester's sent requests then, if that save was successful, save the requester's Id to the target's friend requests.
Do I need to use a third party library like q in order to do this as cleanly as possible? How can I structure this such that I can use the traditional single error handler at the end?
function _addFriend (requesterId, targetId) {
// (integer, integer)
User.findById(requesterId)
.exec((requester) => {
if (!(targetId in requester.friends
|| targetId in requester.sentfriendRequests
|| targetId in requester.friendRequests)) {
requester.sentfriendRequests = requester.sentfriendRequests.concat([targetId])
requester.save()
.then((err) => {
if (err) throw err;
User.findById(targetId)
.exec((err, target) => {
if (err) throw err;
target.friendRequests = target.friendRequests.concat([requesterId])
target.save().then(err => {if (err) throw err})
})
})
}
})
}
You will need some nesting to do conditionals in promise code, but not as much as with callback-based code.
You seem to have messed up a bit of the if (err) throw err; stuff, you should never need that with promises. Just always use .then(result => {…}), and don't pass callbacks to exec any more.
If you always properly return promises from your asynchronous functions (including then callbacks for chaining), you can add the single error handler in the end.
function _addFriend (requesterId, targetId) {
// (integer, integer)
return User.findById(requesterId).exec().then(requester => {
if (targetId in requester.friends
|| targetId in requester.sentfriendRequests
|| targetId in requester.friendRequests) {
return;
}
requester.sentfriendRequests = requester.sentfriendRequests.concat([targetId])
return requester.save().then(() => {
return User.findById(targetId).exec()
}).then(target => {
target.friendRequests = target.friendRequests.concat([requesterId])
return target.save()
});
});
}
_addFriend(…).catch(err => {
…
})
In English, the way to do this is to use the promises returned by exec() have then blocks return promises, un-indent, then add then to those. Much easier to say in code...
EDIT thanks (again) to #Bergi for making me read and understand the app logic. #Bergi is right that there must be a little nesting to get the job done, but the real point isn't about reducing nesting, but about improving clarity.
Better clarity can come from factoring into logical parts, including some that return in promises.
These few functions conceal the promise nesting that's required by the logic. This doesn't specify (because the OP doesn't indicate how the app should handle) what addFriend should return when it refuses to do so due to an existing request...
function _addFriend (requesterId, targetId) {
// note - pass no params to exec(), use it's returned promise
return User.findById(requesterId).exec().then((requester) => {
return canAddFriend(requester, targetId) ? addFriend(requester, targetId) : null;
});
}
function canAddFriend(requester, targetId) {
return requester && targetId &&
!(targetId in requester.friends
|| targetId in requester.sentfriendRequests
|| targetId in requester.friendRequests);
}
function addFriend(requester, targetId) {
requester.sentfriendRequests = requester.sentfriendRequests.concat([targetId]);
return requester.save().then(() => {
return User.findById(targetId).exec();
}).then((target) => {
target.friendRequests = target.friendRequests.concat([requesterId]);
return target.save();
});
}
Once you realise that .exec() returns a promise, you can :
achieve the desired flattening and make the code more readable.
avoid the need to handle errors amongst the "success" code.
handle errors in a terminal .then() or .catch().
As a bonus you can also (more readily) throw meaningful errors for each of those x in y conditions.
Straightforwardly, you could write :
function _addFriend(requesterId, targetId) {
return User.findById(requesterId).exec().then(requester => {
if (targetId in requester.friends) {
throw new Error('target is already a friend');
}
if (targetId in requester.sentfriendRequests) {
throw new Error('friend request already sent to target');
}
if (targetId in requester.friendRequests) {
throw new Error('target already sent a friend request to requester');
}
requester.sentfriendRequests = requester.sentfriendRequests.concat([targetId]); // or just .push()?
return requester.save();
}).then(() => {
return User.findById(targetId).exec().then(target => {
target.friendRequests = target.friendRequests.concat([requesterId]); // or just .push()?
return target.save();
});
});
}
Note the need for returns to control flow.
But you could do even better. As writtten above, the requested stuff could succeed then the target stuff fail, resulting in a db disparity. So what you really want is a db transaction to guarantee that both happen or neither. Mongoose undoubtedly provides for transactions however you can do something client-side to give you something transaction-like with partial benefit.
function _addFriend(requesterId, targetId) {
return Promise.all([User.findById(requesterId).exec(), User.findById(targetId).exec()]).then(([requester, target]) => { // note destructuring
if (targetId in requester.friends) {
throw new Error('target is already a friend');
}
if (targetId in requester.sentfriendRequests) {
throw new Error('friend request already sent to target');
}
if (targetId in requester.friendRequests) {
throw new Error('target already sent a friend request to requester');
}
requester.sentfriendRequests = requester.sentfriendRequests.concat([targetId]);
target.friendRequests = target.friendRequests.concat([requesterId]);
return requester.save().then(() => {
return target.save();
});
});
}
Here, you could still get the (unlikely) situation that the first save is successful and the second save fails, but at least you have the assurance that absolutely nothing happens unless both the requester and target exist.
In both cases, call as follows :
_addFriend(requesterId, targetId).then(function() {
// do whatever on success
}, function(error) {
// do whatever on error
});
Even if you don't use the error messages in the live environment, they could be very useful when testing/debugging. Please check them - I may have gotten them wrong.
Can you recommend how to correctly deal with a control flow with many if/switch and promises? All the tutorials on the Internet that I've found tend to deal with simple control flow, without many (any?) different processing branches. Any suggested reading or at least search terms?
The way I do it now is to encapsulate if/switch logic in a function that returns a Promise after evaluating the conditions and returns to the main process loop. Any way to do it better, nicer?
Sample code:
// Check if argument is a valid URL
Promise.promisify(checkUrl)().then(() => {
// Delete all query parameters from URL if present
return sanitizer.cleanAsync(argv.url)
}).then(_cleanUrl => {
cleanUrl = _cleanUrl;
logger.warn(`URL: ${cleanUrl}`);
// Validate Google Analytics view id supplied as '--gaId=<id>' command line argument or exit if it is not present
return Promise.promisify(checkGaId)()
}).then(() => {
// Check if DB exists, if not create it
return db.checkIfDatabaseExistsAsync()
}).then(() => {
// Check if all tables exist, if not create them
return db.checkTablesAsync()
}).then(() => {
// Check DB integrity (possiblDelete all query parameters from URL if presente to turn off in the config)
if (config.database.checkIntegrity) {
return db.integrityChecksAsync();
}
}).then(() => {
// Check if URL already exists in DB, if not insert it
return db.getOrCreateEntryUrlIdAsync(cleanUrl)
}).then(_entryId => {
entryId = _entryId;
// Check if any previous executions for the entry point exist and if so whether the last one completed
return db.getLastExecutionDataAsync(entryId);
}).then(lastExecution => {
// If last execution was not completed prompt for user action
return processLastExecution(entryId, lastExecution)
}).then(_pages => {
... more code follows here...
And psuedo-code for processLasExecution function:
function processLastExecution(entryId, lastExecution) {
return new Promise(
function (resolve, reject) {
// No previous executions found or all was okay
if (lastExecution == null || (lastExecution != null && lastExecution.is_completed == 'Y')) {
...resolves with A;
} else {
Promise.promisify(selectRunOption)().then(option => {
switch (option) {
case 'resume':
...resolves with B;
break;
case 'ignore':
...resolves with C;
break;
case 'delete':
...resolves with D;
break;
default:
...rejects
}
});
}
}
)
}
Any way of having the if/switch logic better/more clearly encapsulated or served?
Oh, if anyone wonders this is a command line script, not a web application, and this not exactly what Node.js was intended for.
I think it is better to use generator, then you can write sync like codes:
co(function* () {
// Check if argument is a valid URL
if (yield checkUrl) {
var cleanUrl = yield sanitizer.cleanAsync(argv.url);
...
}
...
}, ...
co can cooperation with callback and promise, see https://github.com/tj/co
I'm new to javascript promises and am having difficulty using them with a collection of elements. Within the collection, I perform an operation which returns a promise. Once the entire operation (including all the Promises in the collection) has completed, I need to perform another set of operations. The promises within the collection need to go in sequence.
I have tried the following approach:
public cleanup(onCleanupComplete: any): void {
if (this._app == null) return; //this._app comes out of an external API
// Below line of code won't compile, it is just for illustration.
// I'm trying to show that I need a promise in return from method
this.removeConference(0).then(() => {
// Do additional clean up operation and call onCleanupComplete
onCleanupComplete(true, null);
});
}
private removeConference(i : number) {
if (this._app.conversationsManager.conversations == null
|| i === this.conversationLength)
return; // this.conversationLength equals initial
// number of elements in collection
// How do I return a promise here?
var conversation = this._app.conversationsManager.conversations(0);
console.log("app.cleanup.leave", `Leaving conversation ${conversation}`);
conversation.leave().then(() => {
console.log("app.cleanup.leave", `Conversation ${conversation} left successfully`);
this.app.conversationsManager.conversations.remove(conversation);
_ this.removeConference(i);
});
}
What should I return from removeConference once all the conversations in collection are removed?
So this is something that nabbed me early on in understanding promises. You need to get ALL of your code away from the practice of passing in a callback, and then simply using the promise to call that. Instead, to keep the continuous nature of promises, you want to just return promises to the calling function, unless your function is the one that should decide what to do afterward. So, your code should look something like this.
public cleanup(onCleanupComplete: any):Promise<any> {
if (this._app == null) return; //this._app comes out of an external API
// Below line of code won't compile, it is just for illustration.
// I'm trying to show that I need a promise in return from method
var promiseArray = [];
for (var i = 0; i < this.conversationLength; i++) {
promiseArray.push(removeConference(i));
}
return Promise.all(promiseArray);
}
private removeConference(i : number):Promise<any> {
if (this._app.conversationsManager.conversations == null
|| i === this.conversationLength)
return Promise.resolve(true);
var conversation = this._app.conversationsManager.conversations(0);
console.log("app.cleanup.leave", `Leaving conversation ${conversation}`);
return conversation.leave().then(() => {
console.log("app.cleanup.leave", `Conversation ${conversation} left successfully`);
this.app.conversationsManager.conversations.remove(conversation);
this.removeConference(i);
});
}
I'm not 100% sure this compiles correctly, but hopefully it helps conceptually. Promise.all is really the key function here - it takes an array of promises, and creates a matching "control promise" that only resolves when all of them have.