我是新手,正在尝试rxjs和nestjs。我目前试图完成的用例是出于教育目的。因此,我想使用“ fs”模块读取json文件(如果文件为空或无法读取,则会引发可观察到的错误)。现在,我通过异步读取文件来创建一个可观察对象,在主题中设置观察者,然后在 controller 中订阅该主题。这是我在服务中的代码
@Injectable()
export class NewProviderService {
private serviceSubject: BehaviorSubject<HttpResponseModel[]>;
// this is the variable that should be exposed. make the subject as private
// this allows the service to be the sole propertier to modify the stream and
// not the controller or components
serviceSubject$: Observable<HttpResponseModel[]>;
private serviceErrorSubject: BehaviorSubject<any>;
serviceErrorSubject$: Observable<any>;
filePath: string;
httpResponseObjectArray: HttpResponseModel[];
constructor() {
this.serviceSubject = new BehaviorSubject<HttpResponseModel[]>([]);
this.serviceSubject$ = this.serviceSubject.asObservable();
this.serviceErrorSubject = new BehaviorSubject<any>(null);
this.serviceErrorSubject$ = this.serviceErrorSubject.asObservable();
this.filePath = path.resolve(__dirname, './../../shared/assets/httpTest.json');
}
readFileFromJson() {
return new Promise((resolve, reject) => {
fs.exists(this.filePath.toString(), exists => {
if (exists) {
fs.readFile(this.filePath.toString(), 'utf-8' , (err, data) => {
if (err) {
logger.info('error in reading file', err);
return reject('Error in reading the file' + err.message);
}
logger.info('file read without parsing fg', data.length);
if ((data.length !== 0) && !isNullOrUndefined(data) && data !== null) {
// this.httpResponseObjectArray = JSON.parse(data).HttpTestResponse;
// logger.info('array obj is:', this.httpResponseObjectArray);
logger.info('file read after parsing new', JSON.parse(data));
return resolve(JSON.parse(data).HttpTestResponse);
} else {
return reject(new FileExceptionHandler('no data in file'));
}
});
} else {
return reject(new FileExceptionHandler('file cannot be read at the moment'));
}
});
});
}
getData() {
from(this.readFileFromJson()).pipe(map(data => {
logger.info('data in obs', data);
this.httpResponseObjectArray = data as HttpResponseModel[];
return this.httpResponseObjectArray;
}), catchError(error => {
return Observable.throw(error);
}))
.subscribe(actualData => {
this.serviceSubject.next(actualData);
}, err => {
logger.info('err in sub', typeof err, err);
this.serviceErrorSubject.next(err);
});
}
现在这是控制器类
@Get('/getJsonData')
public async getJsonData(@Req() requestAnimationFrame,@Req() req, @Res() res) {
await this.newService.getData();
this.newService.serviceSubject$.subscribe(data => {
logger.info('data subscribed', data, _.isEmpty(data));
if (!isNullOrUndefined(data) && !_.isEmpty(data)) {
logger.info('coming in');
res.status(HttpStatus.OK).send(data);
res.end();
}
});
}
我面临的问题是,我可以第一次获取文件详细信息,并且订阅被调用一次>运行正常。在随后的请求中
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:470:11)
at ServerResponse.header (C:\personal\Node\test-nest.js\prj-sample\node_modules\express\lib\response.js:767:10)
at Ser
和端点/ getJsonData导致错误。有人可以帮我吗。我相信第一次通话后订阅无法正常进行,但不确定如何结束订阅以及如何解决该问题
答案 0 :(得分:1)
问题是您要在控制器中订阅serviceSubject
。每次发出新值时,它将尝试发送响应。第一次有效,但是第二次它将告诉您不能再次发送相同的响应。该请求已被处理。
您可以使用管道式first()
运算符在第一个值之后完成Observable:
@Get('/getJsonData')
public async getJsonData() {
await this.newService.getData();
return this.newService.serviceSubject$.pipe(first())
}
您希望Observable
被共享(热),以便每个订户始终获得相同的最新价值。这就是BehaviourSubject
所做的。因此,当您公开公开Subject
到Observable
时,请勿将其转换为Subject
,因为这样会丢失您期望的行为。相反,您可以将Observable
强制转换为next()
,这样在内部它仍然是一个主题,但不会公开private serviceSubject: BehaviorSubject<HttpResponseModel[]>;
get serviceSubject$(): Observable<HttpResponseModel[]> {
return this.serviceSubject;
}
方法来公开发出新值:
template<typename T>
struct point
{
T x, y, z;
template<typename T1>
inline point<T> operator*(const point<T1>& p) const // multiply by another point.
{
return point<T>{this->x*p.x, this->y*p.y, this->z*p.z};
}
template<typename T1>
inline point<T> operator*(const T1& v) const // multiply by constant from right side
{
return point<T>{this->x*v, this->y*v, this->z*v};
}
}
template<typename T1, typename T2>
inline point<T1> operator*(const T2& v, const point<T1>& p) // multiply by a constant from the left side.
{
return point<T1>{p.x*v, p.y*v, p.z*v};
}
答案 1 :(得分:0)
我建议直接将Promise
返回给控制器。在这里,您不需要Observable
。对于订阅者,您还向Promise
发送serviceSubject
的值。
async getData() {
try {
const data = await this.readFileFromJson();
this.serviceSubject.next(data as HttpResponseModel[]);
return data;
} catch (error) {
// handle error
}
}
在您的控制器中,您只需返回Promise
:
@Get('/getJsonData')
public async getJsonData() {
return this.newService.getData();
}
答案 2 :(得分:0)
我认为尝试将冷的可观察变量(我创建的可观察变量)转换为热/暖的可观察变量可能有助于将插件插入到单个源中并发出并完成其执行,并将最后发出的数据保持为任何克隆值。因此,我可以使用publishLast()和refCount()运算符将冷的可观察性变为温暖的可观察性,并且可以实现可观察性的单个预订和执行完成。这是我所做的更改。
这是我所做的服务类别更改
getData() {
return from(this.readFileFromJson()).pipe(map(data => {
logger.info('data in obs', data);
this.httpResponseObjectArray = data as HttpResponseModel[];
return this.httpResponseObjectArray;
}), publishLast(), refCount()
, catchError(error => {
return Observable.throw(error);
}));
// .subscribe(actualData => {
// this.serviceSubject.next(actualData);
// }, err => {
// logger.info('err in sub', typeof err, err);
// this.serviceErrorSubject.next(err);
// });
}
这是我在控制器中所做的更改
public async getJsonData(@Req() req, @Res() res) {
let jsonData: HttpResponseModel[];
await this.newService.getData().subscribe(data => {
logger.info('dddd', data);
res.send(data);
});
}
也欢迎任何允许可观察对象首先订阅主题然后在控制器中订阅该主题的答案。
我发现了一篇关于热与冷可观察物的好文章,以及如何使可观察物订阅一个来源并将冷转变为热/热可观察物-https://blog.thoughtram.io/angular/2016/06/16/cold-vs-hot-observables.html