【问题标题】:How to return Observable inside a subscription如何在订阅中返回 Observable
【发布时间】:2018-09-01 09:02:06
【问题描述】:

我想在一个 observable 中调用 http 请求,从数据库中进行选择操作。我做了两个服务,DbServiceBackendService

BackendService 发出 http post 请求并返回响应数据。在我的设计中,BackendService 应该订阅 DbService 以获取 url,然后发出 http post 请求然后返回响应数据。

BackendService 可以从 DbService 获取 url 并尝试发出 http 请求但不能。响应数据为(Json 格式)

{"_isScalar":false,"source":{"_isScalar":false},"operator":{}}

我不明白这里发生了什么。我的服务和 AppComponent 文件如下。

有后端服务

import { Injectable } from "@angular/core";
import { getString, setString } from "application-settings";
import { Headers, Http, Response, RequestOptions } from "@angular/http";
import { Observable } from "rxjs/Observable";
import 'rxjs/add/observable/of';
import "rxjs/add/operator/do";
import "rxjs/add/operator/map";
import "rxjs/add/observable/throw";
import "rxjs/add/operator/catch";
import { DbService } from "./db.service";

@Injectable()
export class BackendService {
    static BaseUrl= "http://blabla.com"

    constructor(public http: Http, private db: DbService) {
    }

        sendPost(key: string, requestObj: Object):Observable<any>{
        console.log("sendPost: ");
        return new Observable(obs=> {
            let obs1 = this.db.getActionUrl(key);
            obs1.subscribe(value => {
                let url = BackendService.BaseUrl + value;

                console.log("key: ", key);
                console.log("url: ", url);
                var h = BackendService.getHeaders();
                obs.next(this.http.post(
                    url,
                    JSON.stringify(requestObj),
                    { headers: h }
                ).map((res: Response) => res.json()));
                // .catch((error: any) => Observable.throw(error.json())));
                obs.complete();
            }
            , error => {
                console.error("send post error: "+ error);
                obs.error(error);
            }
        );
        });
    }

    static getHeaders() {
        let headers = new Headers();
        headers.append("Content-Type", "application/json");
        headers.append("SESSION-ID", this.sessionId);
        // headers.append("Authorization", BackendService.appUserHeader);
        return headers;
    }
}

有DbService

import { Injectable } from "@angular/core";
import { Observable } from "rxjs/Observable";
import 'rxjs/add/observable/of';
import "rxjs/add/operator/do";
import "rxjs/add/operator/map";
import "rxjs/add/observable/throw";
import "rxjs/add/operator/catch";
import 'rxjs/add/operator/toPromise';

var Sqlite = require("nativescript-sqlite");

@Injectable()
export class DbService {
    private tableActions = "actions";

    private columnActionName = "name";
    private columnActionUrl = "url";


    private database: any;
    constructor() {
        console.log("DbService Constructor");
        (new Sqlite("my_app.db")).then(db => {
            db.execSQL("CREATE TABLE IF NOT EXISTS " + this.tableActions + " (" + this.columnActionName + " TEXT PRIMARY KEY, " + this.columnActionUrl +" TEXT)").then(id => {
                this.database = db;
                console.log("DB SERVICE READY");
            }, error => {
                console.log("CREATE TABLE ERROR", error);
            });
        }, error => {
            console.log("OPEN DB ERROR", error);
        });
    }


    public getActionUrl(key: string):Observable<any>{
    return new Observable(observer => { 
        if (key === "getApiMap") {
            observer.next("/getApiMap");
            observer.complete();
            return;
        }
        console.log("getActionUrl :" + key);
        this.database.all("SELECT * FROM " + this.tableActions).then(
            rows => {
                console.log(rows);
                observer.next(rows[0][this.columnActionUrl]);
                observer.complete();
            }, error => {
                console.log("SELECT ERROR: getActionUrl: ", error);
                observer.error(error);
            })
    });
    }
}

还有我的 AppComponent 发出 http 请求...

//some imports

export class AppComponent {
    public resp: Observable<ModelMainGetApiMapRes>;
    public constructor(private bs: BackendService, private db: DbService) {
let req = new ModelMainGetApiMapReq()
    bs.sendPost("getApiMap", req, false).subscribe(
        (res: ModelMainGetApiMapRes) => {
            console.log("getapimap response received!");
            console.log(JSON.stringify(res));
            console.log("apimap version:" + res.api_version);

        },
        err => {
             console.error("error!", err);
        }
    );
 }

//some functions
}

app.component 的控制台输出是

CONSOLE LOG file:///app/shared/backend.service.js:61:20: sendPost:
CONSOLE LOG file:///app/shared/backend.service.js:66:28: key:  getApiMap
CONSOLE LOG file:///app/shared/backend.service.js:67:28: url:  http://blabla.com/getApiMap
CONSOLE LOG file:///app/app.component.js:55:36: getapimap response received!
CONSOLE LOG file:///app/app.component.js:56:36: {"_isScalar":false,"source":{"_isScalar":false},"operator":{}}
CONSOLE LOG file:///app/tns_modules/tns-core-modules/profiling/profiling.js:10:16: ANGULAR BOOTSTRAP DONE. 7805.849
CONSOLE ERROR file:///app/tns_modules/@angular/core/bundles/core.umd.js:1486:24: ERROR Error: Uncaught (in promise): TypeError: undefined is not an object (evaluating 'res.api_version')

【问题讨论】:

  • 问题出在问题上。你不能也不应该那样做。使用运算符来组合可观察对象(不确定哪一个适合您的情况,可能是 switchMap),然后订阅生成的可观察对象。由于您正在处理无法完全从可观察对象中受益的事情,因此切换到承诺可能是合理的(async..await 将比可观察汤更顺畅)。
  • 这个答案包含解决方案stackoverflow.com/a/62385358/5535003

标签: angular typescript nativescript angular2-observables


【解决方案1】:

BackendService.ts 中使用您的实际代码:

return new Observable(obs=> {
    let obs1 = this.db.getActionUrl(key);
    obs1.subscribe(value => {
        let url = BackendService.BaseUrl + value;

        console.log("key: ", key);
        console.log("url: ", url);
        var h = BackendService.getHeaders();
        obs.next(this.http.post(
            url,
            JSON.stringify(requestObj),
            { headers: h }
        ).map((res: Response) => res.json()));
         obs.complete();
        ...
    });
 });

你发出了 http observable

this.http.post(
                    url,
                    JSON.stringify(requestObj),
                    { headers: h }
                ).map((res: Response) => res.json())

这就是为什么你得到:{"_isScalar":false,"source":{"_isScalar":false},"operator":{}} 订阅它时,它是一个可观察的。

最简单的代码解决方案,您可以在订阅第二个 observable 后发出数据,例如:

return new Observable(obs=> {
    let obs1 = this.db.getActionUrl(key);
    obs1.subscribe(value => {
        let url = BackendService.BaseUrl + value;
        console.log("key: ", key);
        console.log("url: ", url);
        var h = BackendService.getHeaders();
        this.http.post(
            url,
            JSON.stringify(requestObj),
            { headers: h }
        ).map((res: Response) => res.json())
         .subscribe(data => obs.next(data));
    });
});

但更好的解决方案是使用 switchMap 运算符:(或任何其他 xxxxMap 运算符)

import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/map';
...
sendPost(key: string, requestObj: Object):Observable<any>{
    return this.db.getActionUrl(key)
            .map( value => BackendService.BaseUrl + value)
            .switchMap(url => this.http.post(
                url,
                JSON.stringify(requestObj),
                { headers: h }
            )
            .map((res: Response) => res.json()))
}

【讨论】:

  • 感谢您的回复,但您的两个建议均无效...尝试使用切换地图时,我在终端 CONSOLE ERROR file:///app/tns_modules/ 中遇到此错误@angular/core/bundles/core.umd.js:1486:24:错误错误:未捕获(承诺中):TypeError:未定义不是函数(靠近'..switchMap(函数(url){re.. .')" 并且当使用其他代码(使用 obs.emit ...)时,它说找不到订阅者的发射 ...,我试图修改您的建议,例如 .subscribe(data => {obs.next(data) ; obs.complete()}); 但它没有工作,等待运行时......
  • @AsyncTask Mhh 可能你需要从 import 'rxjs/add/operator/switchMap'; 导入 swtchMap 检查这个 plunker 是否相同:embed.plnkr.co/rl85Y2fP2Pb2LZjXyNdk
  • 你说得对!添加 switchMap 导入后它起作用了。那么,您对其他方式(使用发射方法)有任何想法吗?
  • @AsyncTask 现在应该可以工作了,有一个混淆,应该使用 obs.next() 而不是 obs.emit()
猜你喜欢
  • 1970-01-01
  • 2017-02-17
  • 1970-01-01
  • 2017-03-26
  • 2019-03-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多