Is there a way to await for the first value returned from a subscribe call?
For example, in the following:
async getValue(...) {
myObs$ = ...
let val = await myObs$.pipe(first()).toPromise()
myObs$.subscribe(val => {
this.value = val
})
return val
}
The async function will return a Promise of val, but I also want to subscribe and get a data member value that will be updated whenever the observable myObs$ emits a new value.
My question: is there away to return a Promise without calling the line with the first() call and just wait for the first time I get the result in subscribe?
There's no real reason here that this method needs to be async as the caller will have to await it or use .then() regardless. You could simply rewrite it as:
getValue(...) {
myObs$ = ...
myObs$.subscribe(val => {
this.value = val
})
return myObs$.pipe(first()).toPromise()
}
This will keep your internal subscription around and allow it to keep receiving values while the caller of getValue will receive a Promise of the first emitted value.
Related
I have a subject which emits a string value and the code is as below: when the components get initialized, the subjectTypeSubject is null. But there is another method in a component get subscribed to this observable where i set isLoading to true. Because the finalize is not getting called, the loading is always set to true. How to make it work so it gets completed when the value is null as well.
private subjectTypeSubject = new BehaviorSubject<string>(null);
private getPage() {
this.subjectTypeSubject.pipe(
filter((selectedSubjectType) => {
console.log('subject type', selectedSubjectType); //first time it comes as null. so it wont go inside switchmap.
return selectedSubjectType && selectedSubjectType !== '';
}),
switchMap((selectedSubjectType) => {
return this.customListsService
.getCustomListItemsByTypeName()
}),
map((customItemsData) => {
return customItemsData
})
);
}
private _getPage(pageNumber: number, search: string) {
this.loading = true;
this._pageSubscription = this.getPage({
pageSize: this._config.pageSize,
pageNumber,
search
})
.pipe(finalize(() => (this.loading = false))) //this is not called
.subscribe((p) => {
this._currentPage = p.pageNumber;
this.options = p.options;
this._allLoaded = p.isLast;
this.loading = false;
});
}
Adding a takeWhile() instead of filter worked for me. If there is any other better solution. please let me know. thanks
BehaviorSubject doesn't complete unless you complete it
There are multiple ways to call complete an observable in a pipe. take, takeWhile and takeUntil are some of them. Calling .complete on the BehaviorSubject is also an option.
But you should ask yourself: is this really what you want to achieve here? After completion it's not possible to pass any data to the subscription, even if the initial BehaviorSubject emits a new value.
One thing that this strange about your code: it should not work at all. In getPage() you are creating a new observable (by piping the BehaviorSubject), but you are not returning it. Therefore it should return undefined. It‘s also a little bit odd that you are using pipe in a function call. You should either declare the pipe during initialization or directly subscribe to a newly created observable.
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 2 years ago.
I'm hoping somebody can point out the error of my ways here.
I have two functions at the moment. One is getData and it's an async function that simply makes an API call requesting data. The second function is getRandomCategories that encapsulates the getData function call and holds the value of that async operation in a variable called res. The rest of the code within getRandomCategories manipulates the response data to an array of numbers where each number represents a category.
When I use the debugger statement in the getRandomCategories function (right before the return statement within the try block) I'm getting the data type I'm expecting from my variable named apiCallCategoryArray - it's an array of numbers each representing a category. Life is good.
Here's the rub. When I call getRandomCategories expecting the dataArray variable (at the bottom of the code snippet) to hold an array of numbers - I'm getting a Promise back with its state pending??
I'm not understanding why the value for apiCallCategoryArray variable is showing up as my expected value using the debugger (and thus I'm returning it within the function) but I'm not able to access that value when I call the function. Why am I getting a Promise back with a pending state? What am I missing?
Here's my code below:
async function getData(endpoint, query, value) {
return await axios.get(
`http://jservice.io/api/${endpoint}?&${query}=${value}`
)
}
// createa a function that will return 6 random categories
async function getRandomCategories() {
try {
const res = await getData('categories', 'count', 50)
const data = res.data;
const categories = filterCategoryData(data); // I'm filtering for category id with clues_count === 5
const categoryIdArr = mapCategoryIds(categories); // an array of just category Ids
const shuffledCategoryIds = shuffle(categoryIdArr);
const apiCallCategoryArray = takeFirstXItems(shuffledCategoryIds, 6);
debugger// the value is what I'm expecting an array of numbers with length = 6
return apiCallCategoryArray
} catch (err) {
console.log(err);
}
}
//Solution one: Does not work. I'm getting a promise back instead of an array of numbers
const dataArray = getRandomCategories()
console.log(dataArray) // Promise { <state>: "pending" }
// expected return value [12231, 12344, 343245,124041, 12344, 348855] array of numbers
// Solution two: Does not work either. I'm still getttng a promise back instead of an array of numbers
const dataArray2 = getRandomCategories().then((array) => {
return array
})
console.log(dataArray2) // Promise { <state>: "pending" }
// expected return value [12231, 12344, 343245,124041, 12344, 348855] array of numbers
My objective is for my dataArray variable to hold an array of numbers (not a Promise with pending) returned by calling getRandomCategories(). So I can use this value for other functions in my code.
Thanks in advance for your time and responses.
you need to use use async, await to get back your Promise data. like this
async function test(){
let dataArray2 = await getRandomCategories();
console.log(dataArray2);
};
I have a problem with return Observable from second request.
Something like this:
commandRequest(action:string, commandData:any):Observable<CashDesckResponse>
{
let command:CashDeskRequest;
//ask my backend for command
this.requestCommandString(action, commandData, "POST")
//this is my response from backend
.map(r=>r.response).subscribe(my_1_response=>{
//then i need to send this response data to other server/action
//to execute it and return this second response as the result of
//this method.
command = new CashDesckRequest(my_1_response);
return this.server.executeCommand(command).map(return_this=>return_this);
});
};
private requestCommandString(action:string, requestData:any,
type:string):Observable<AjaxResponse>{
return Observable.ajax({
body:JSON.stringify(requestData),
method:type,
url:this._apiServerUrl+action
}).map(r=>r);
};
My problem that the commandRequest return value from first .map(). And if i`ll try to return value from inner subscribe compiller throw error:
[ts] A function whose declared type is neither 'void' nor 'any' must return a value.
https://habrastorage.org/web/93b/e6b/17b/93be6b17bf5b46b5847e67deefd6eea1.png
To map from one stream to another you can use mergeMap or switchMap
commandRequest(action:string, commandData:any):Observable<CashDesckResponse>
{
return this.requestCommandString(action, commandData, "POST")
.map(r1 => new CashDesckRequest(r1.response)
// map first response to command instance
.mergeMap(command =>this.server.executeCommand(command));
// map the command observable to a new observable
}
I am building a service which exposes an Observable. In this service I receive external function calls which should trigger a next call on the Observable so that various consumers get the subscribe event. During Observer constructor I can call next and everything works great, but how can I access this outside of the constructor so that external triggers can fire next calls?
private myObservable$: Observable<any>;
During service init I do
this.myObservable$ = new Observable(observer => {
observer.next("initial message");
}
Then in other methods of the same service I want to be able to execute something like
this.myObservable$.observer.next("next message");
The above obviously doesn't work, but how can I accomplish this goal?
I'm assuming I'm missing something basic since there must be a way to emit further messages outside of the Observable's initial constructor
You should create a Subject for that
this.myObservable$ = new Subject();
And then you can call at any point:
this.myObservable$.next(...);
Or use subscribe:
this.myObservable$.subscribe(...)
Actually Subject is used for both publisher and subscriber, and here I think you need only to publish your value, so simply use Observable.
By using observable, assign Subscriber to class level variable and then use it, like below code
subscriber: Subscriber<boolean>;
public observe(): Observable<boolean> {
return new Observable<boolean>(subs => {
this.subscriber = subs;
});
}
public callNext() {
if (this.subscriber) {
this.subscriber.next();
this.subscriber.complete();
}
}
Two ways:
Make myObservable$ public:
public myObservable$: Observable;
Encapsulate the observable in a subject stream, and provide a helper to call next:
export class TestService {
public myObservable$: Observable;
private _myObservableSubject: Subject;
constructor() {
this._myObservableSubject = new Subject();
this.myObservable$ = this._myObservableSubject.asObservable();
}
public NextMessage(message?: string): void {
this._myObservableSubject.next(message);
}
}
Observable: You have to call the next() function from inside the constructor and only one time you can subscribe
message = new Observable((observer)=>{
observer.next(9);
})
this.messsage.subscribe((res)=>{
console.log(res)
})
output: 9
Subject: You have to call next() function from outside the constructor and multiple times you can subscribe.
The subject does not store any initial value before subscribe.
messsage = new Subject()
this.messsage.next(3)
this.messsage.subscribe((res)=>{
console.log(' A '+res)
})
this.messsage.next(4)
this.messsage.next(5)
this.messsage.subscribe((res)=>{
console.log(' B '+res)
})
this.messsage.next(6)
output:
A 4
A 5
A 6
B 6
BehaviorSubject: You have to call next() function from outside the constructor and multiple times you can subscribe.
The BehaviorSubject does store only one initial value before subscribe.
messsage = new BehaviorSubject ()
this.messsage.next(3)
this.messsage.subscribe((res)=>{
console.log(' A '+res)
})
this.messsage.next(4)
this.messsage.next(5)
this.messsage.subscribe((res)=>{
console.log(' B '+res)
})
this.messsage.next(6)
output:
A 3
A 4
A 5
B 5
A 6
B 6
I ended up combining a couple of things:
olsn's answer, which nicely demonstrates Subject's ease of use
Ravi's answer, which correctly points out that we only want an Observable exposed
a more functional approach, because this and Class give me the shivers
TypeScript typings for generic use
const createObservableWithNext = <T>(): {
observable: Observable<T>;
next: (value: T) => void;
} => {
const subject = new Subject<T>();
const observable = subject.asObservable();
const next = (value: T) => subject.next(value);
return {
observable,
next,
};
};
I'm trying to create observable stream which takes user id from cookie and, if not found in cookie, fetches it from API. How can I do it in RxJS?
var userIdRequest = Rx.Observable.bindCallback(generateIdAsync);
var cookieUserIdStream = Rx.Observable.of(getCookieValue("user_id"))
.filter(x => x !== null);
var userIdStream = cookieUserIdStream.__ifEmptyThen__(userIdRequest()); // <<< ???
// Emulating async request for user id
// Will be a JSONp call in real app
function generateIdAsync(cb) {
setTimeout(() => {
cb(`id_${new Date().getTime()}`);
}, 300);
}
function getCookieValue(name) {
var regexp = new RegExp(`${name}=([^;]*)`);
var match = document.cookie.match(regexp);
return match && match[1];
}
There's a defaultIfEmpty method which works with simple values only, not with observables. In Bacon.js there's or method for streams, which works perfectly fine, but I don't see anything similar in RxJS. Do I miss something or do I need to implement a custom observer?
You may concat the 2 observables and get the first emitted value:
var userIdStream = Rx.Observable.concat(cookieUserIdStream, userIdRequest).first();