Does Rxjs provide a more compact function equivalent to this call?
someObservable.pipe(take(1)).toPromise()
toPromise() is actually deprecated now (at least in RxJs 7.x). You can use firstValueFrom(), which will return a Promise.
In your case you could write:
#import { firstValueFrom } from 'rxjs';
export class example {
async myFunction<T>(someObservable$: Observable<T>): Promise<T> {
return await firstValueFrom(someObservable$);
}
}
or use it with .then(), e.g.
#import { firstValueFrom } from 'rxjs';
export class example {
myFunction<T>(someObservable$: Observable<T>): Promise<T> {
return firstValueFrom(someObservable$).then((value) => {
return value;
});
}
}
See here: https://rxjs.dev/api/index/function/firstValueFrom#firstvaluefrom
Related
EventEmitter in Nestjs is wrapper around EventEmitter2 module. I whant that Server-Sent Events return Observable with EE.
import { Controller, Post, Body, Sse } from '#nestjs/common';
import { fromEvent } from 'rxjs';
import { EventEmitter2 } from '#nestjs/event-emitter';
import { OrdersService } from './orders.service';
import { CreateOrderDto } from './dto/create-order.dto';
#Controller('orders')
export class OrdersController {
constructor(private ordersService: OrdersService,
private eventEmitter2: EventEmitter2) {}
#Post()
createOrder(#Body() createOrderDto: CreateOrderDto) {
// save `Order` in Mongo
const newOrder = this.ordersService.save(createOrderDto);
// emit event with new order
this.eventEmitter2.emit('order.created', newOrder);
return newOrder;
}
#Sse('newOrders')
listenToTheNewOrders() {
// return Observable from EventEmitter2
return fromEvent(this.eventEmitter2, 'order.created');
}
}
But after subscribtion to this source from browser i've getting only errors
this.eventSource = new EventSource('http://localhost:3000/api/v1/orders/newOrders');
this.eventSource.addEventListener('open', (o) => {
console.log("The connection has been established.");
});
this.eventSource.addEventListener('error', (e) => {
console.log("Some erorro has happened");
console.log(e);
});
this.eventSource.addEventListener('message', (m) => {
const newOder = JSON.parse(m.data);
console.log(newOder);
});
It's quite likely that you forgot to format the event in the right way.
For SSE to work internally, each chunk needs to be a string of such format: data: <your_message>\n\n - whitespaces do matter here. See MDN reference.
With Nest.js, you don't need to create such message manually - you just need to return a JSON in the right structure.
So in your example:
#Sse('newOrders')
listenToTheNewOrders() {
// return Observable from EventEmitter2
return fromEvent(this.eventEmitter2, 'order.created');
}
would have to be adjusted to, for example:
#Sse('newOrders')
listenToTheNewOrders() {
// return Observable from EventEmitter2
return fromEvent(this.eventEmitter2, 'order.created')
.pipe(map((_) => ({ data: { newOrder } })));
}
the structure { data: { newOrder } } is key here. This will be later translated by Nest.js to earlier mentioned data: ${newOrder}\n\n
I am failing to understand how to map the data properties out of HttpService in my NestJS application. To my understanding, this Observable just wraps axios. Here's some example code:
interface Todo {
task: string,
completed: false
}
import {
Injectable,
HttpService,
Logger,
NotFoundException,
} from '#nestjs/common'
import { map } from 'rxjs/operators
async getTodo(todoUrl: string): Todo {
const resp = this.httpService
.get('https://example.com/todo_json')
.pipe(map(response => response.data)) // map task/completed properties?
return resp
}
resp in this case seems to be of type Observable. How do I retrieve just the data properties I want using map on this request to return my Todo interface?
Nest by default will subscribe to the observable for you it your return the Observable from your service. As this can be the case you can do something like
#Injectable()
export class TodoService {
constructor(private readonly http: HttpService) {}
getTodos(todoUrl: string): Observable<Todo> {
return this.http.get(todoUrl).pipe(
map(resp => resp.data),
);
}
}
And so long as you have a controller class calling this.todoSerivce.getTodos(todoUrl) and returning it, the response will be sent out.
However, if you want to instead make it a promise as you are more accustomed to them, you can tack on a .toPromise() method to the observable chain and now it it awaitable (though it will be slower because it has to wait for the observable to emit its complete event).
Example with .toPromise():
#Injectable()
export class TodoService {
constructor(private readonly http: HttpService) {}
getTodos(todoUrl: string): Todo {
const myTodo = await this.http.get(todoUrl).pipe(
map(resp => resp.data),
).toPromise();
return myTodo;
}
}
Edit 1/20/22
In RxJS#^7, toPromise() is deprecated and will be removed in v8. Instead, you can use lastValueFrom to wrap the entire observable
#Injectable()
export class TodoService {
constructor(private readonly http: HttpService) {}
getTodos(todoUrl: string): Todo {
const myTodo = await lastValueFrom(this.http.get(todoUrl).pipe(
map(resp => resp.data),
));
return myTodo;
}
}
Looking at your code:
import { map } from 'rxjs/operators'
async getTodo(todoUrl: string): Todo {
const resp = this.httpService
.get('https://example.com/todo_json')
.pipe(map(response => response.data)) // map task/completed properties?
return resp
}
getTodo returns an Observable, not the response. So your return value should be Observable<Todo>.
The code should look more like this:
getTodo(): Observable<Todo> {
return this.http.get<Todo>('https://example.com/todo_json')
.pipe(
map(response => response.data),
catchError(this.handleError)
);
}
EDIT: You can't just return the data from this method because it is asynchronous. It does not have the data yet. The method returns an Observable ... which is basically a contract saying that it will (at some later time) return the data for you.
Async functions need to return a promise, you can call toPromise on an observable to return a promise.
async getTodo(todoUrl: string): Todo {
const resp = this.httpService
.get('https://example.com/todo_json')
.pipe(map(response => response.data)) // map task/completed properties?
return resp.toPromise();
}
async getTodo(todoUrl: string): Todo {
const resp = await this.httpService
.get('https://example.com/todo_json')
.toPromise();
return resp.data;
}
Trying to understand what is difference between these all with respect to axios library.
function doSomething() {
axios.get(url)
.then((response) => process(response))
.catch((error) => handle(error))
}
vs
async function doSomething() {
try {
const response = await axios.get(url);
process(response);
} catch(error) {
handle(error);
}
}
vs
async function doSomething() {
return await axios.get(url);
}
vs
function doSomething() {
return axios.get(url);
}
Trying to understands this and write wrapper for axios calls for the consumer.
Thanks.
If you use a function that returns different observable like:
const load = () => {
if (!activated) {
return of(null);
} else {
return of(123);
}
}
And if you use combineLatest, it will always the the returned value at that moment, even if you change the activated to true.
combineLatest(load(), b)
.pipe(map(([num, str]) => `${num}:${str}`))
.subscribe(data => log(`result: ${data}`))
b.next('a');
activated = true;
b.next('b'); // should log "123:b", but it doesn't
You can check the full example here: https://stackblitz.com/edit/combinelatest-dynamically
Any solution to always get the updated version?
ps: I cannot have a single Subscription, cause it's a middleware from localStorage
You just need to make activated observable and have it feed load(). The following is modified from your stackblitz.
import { ReplaySubject } from 'rxjs/ReplaySubject';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { combineLatest } from 'rxjs/observable/combineLatest';
import {merge} from "rxjs/observable/merge";
import { of } from 'rxjs/observable/of';
import { defer } from 'rxjs/observable/defer';
import { map, switchMap } from 'rxjs/operators';
const activated = new BehaviorSubject<boolean>(false);
const b = new ReplaySubject<any>();
const load = (activated) => {
if (!activated) {
return of(null);
} else {
return of(123);
}
}
combineLatest(defer(() => activated.pipe(switchMap(x => load(x)))), b)
.pipe(map(([num, str]) => `${num}:${str}`))
.subscribe(data => log(`result: ${data}`))
b.next('a');
activated.next(true);
b.next('b'); // should log "123:b", but it doesn't
// Utils
function log(v) {
document.body.querySelector('pre').appendChild(document.createElement('div')).textContent = v;
}
Based on the docs for RxJS's Observable.from(), it sounds like you should be able to pass it an object that implements the observable interface. However, the following
const observable = {
subscribe(observer) {
const subscription = someAsyncProcess(res => observer.next(res));
return {
unsubscribe() {
subscription.unsubscribe();
}
}
}
};
Rx.Observable.from(observable)
.subscribe({
next(res) {
console.log(res);
}
});
throws the error
Uncaught TypeError: object is not observable
Is my observable implementation incorrect? Or am I misunderstanding from?
Note: this is more of an academic question about the Observable interface--I realize Observable.create() would work in the above situation.
You can "trick" RxJS into thinking that the object you're passing it is a real Observable by implementing a "symbol function" (I don't know what is the proper name for this). However, you probably never need to do this in practise and it's better to use Observable.create.
const Rx = require('rxjs/Rx');
const Symbol_observable = Rx.Symbol.observable;
const Observable = Rx.Observable;
const observable = {
[Symbol_observable]: function() {
return this;
},
subscribe: function(observer) {
// const subscription = someAsyncProcess(res => observer.next(res));
observer.next(42);
return {
unsubscribe() {
subscription.unsubscribe();
}
}
}
};
Observable.from(observable)
.subscribe({
next(res) {
console.log('Next:', res);
}
});
This prints:
Next: 42
You can use Observable.from if it is an array of events or Observable.of if it is a simple object. It doesn't have to be implementing any interface. The code below is printing a in the console.
Rx.Observable.from("a").subscribe(data=> console.log(data));