Как вернуть значение из функции, внутри которой есть подписка на Observable?


98

Я не знаю, как извлечь значение из Observable, которое будет возвращено функцией, в которой присутствует Observable. Мне нужно вернуть только его значение, ничего больше.

Текущая версия, которая работает

function getValueFromObservable() {
    this.store.subscribe(
        (data:any) => {
            console.log(data)
        }
    )
}
getValueFromObservable()

Мне нужно, чтобы это работало, функция возвращала значение, а затем:

function getValueFromObservable() {
    this.store.subscribe(
        (data:any) => {
            return data
        }
    )
}
console.log(getValueFromObservable())

Что я здесь делаю не так?


2
Вы должны вернуть Observable / Promise и передать через него данные, когда ваша наблюдаемая будет разрешена
гальван

2
Вы можете написать для этого какой-нибудь простой код?
Teddy

6
То, что вы пытаетесь достичь, - это антипаттерн: вы пытаетесь «синхронизировать» асинхронную задачу. Наблюдаемые должны работать не так. Короче говоря, в большинстве случаев функция, имеющая наблюдаемое на входе, также должна возвращать наблюдаемое - или ничего не возвращать. А когда вам нужно что-то сделать с выводом, подписывайтесь на него. В этом случае, если вы хотите сохранить данные в console.log, просто сделайте это внутриsubscribe
Кан Нгуен

1
Я понимаю все, что вы сказали. Я просто использую консольный журнал в качестве демонстрации, я буду использовать эти данные в дальнейшем, поэтому мне нужно, чтобы консольный журнал находился вне наблюдаемого. Дело в том, чтобы иметь функцию, которая, когда вы можете подписаться на наблюдаемое, получать данные, отказываться от подписки и возвращать данные в этой функции, чтобы я мог использовать эти данные в дальнейшем. Я знаю, что это антипаттерн, но мне нужно, чтобы он работал. Любая помощь приветствуется. Текущее мое решение работает, но я не слишком уверен в этом.
Teddy

4
Внимание! Код из раздела «РЕШЕНИЕ» абсолютно неверен. Не используйте это! Он будет работать, только если раздел this.store.subscribe ((data: any) => {output = data}) .unsubscribe () будет завершен до возврата. В противном случае он вернет undefined.
Родион Головушкин

Ответы:


58

РЕДАКТИРОВАТЬ: обновленный код, чтобы отразить изменения, внесенные в способ работы каналов в более поздних версиях RXJS. Все операторы (возьмем мой пример) теперь заключены в оператор pipe ().

Я понимаю, что этот вопрос был довольно давно, и у вас наверняка есть подходящее решение, но для тех, кто ищет это, я бы предложил решить его с помощью обещания сохранить асинхронный шаблон.

Более подробная версия создаст новое обещание:

function getValueFromObservable() {
    return new Promise(resolve=>{
        this.store.pipe(
           take(1) //useful if you need the data once and don't want to manually cancel the subscription again
         )
         .subscribe(
            (data:any) => {
                console.log(data);
                resolve(data);
         })
    })
}

На принимающей стороне вам нужно будет «подождать», пока обещание разрешится, примерно так:

getValueFromObservable()
   .then((data:any)=>{
   //... continue with anything depending on "data" after the Promise has resolved
})

Более тонкое решение могло бы использовать вместо этого RxJS '.toPromise ():

function getValueFromObservable() {
    return this.store.pipe(take(1))
       .toPromise()   
}

Принимающая сторона, конечно же, остается такой же, как и выше.


Каков тип возвращаемого значения вашей getValueFromObservableфункции?
Hardywang

Должно быть обещание с любым типом данных магазина. например, Promise <StoreType>
jparg 03

4
вы по-прежнему возвращаете обещание, которое необходимо выполнить, и не возвращаете значение напрямую.
Roj

1
Свойство take не существует для типа Observable <>
Memmo

1
@Memmo попробуйте .pipe (take (1)) вместо
Тибо

20

Это не совсем правильная идея использования Observable

В компоненте вы должны объявить член класса, который будет содержать объект (то, что вы собираетесь использовать в своем компоненте)

export class MyComponent {
  name: string = "";
}

Тогда a Serviceвернет вам Observable:

getValueFromObservable():Observable<string> {
    return this.store.map(res => res.json());
}

Component должен подготовиться к извлечению из него значения:

OnInit(){
  this.yourServiceName.getValueFromObservable()
    .subscribe(res => this.name = res.name)
}

Вы должны присвоить значение Observableпеременной:

И ваш шаблон будет использовать переменную name:

<div> {{ name }} </div>

Другой способ использования Observable- через asyncтрубу http://briantroncone.com/?p=623

Примечание . Если это не то, о чем вы спрашиваете, обновите свой вопрос, указав более подробную информацию.


Не совсем так. Проблема в том, что данные фиксируются внутри наблюдаемого, и я могу просто записать их в консоль. Я хочу вернуть это значение и console.log или что-то еще из другого файла, вызвав функцию, в которой он находится.
Teddy

Андрей указал, как сделать nameдоступным вне обратного вызова, назначив его nameпеременной компонента . В nameвашем случае возврат синхронно невозможен .
Мэтт

@Matt: Я не могу использовать его Oninitтак, что, если мне нужно вернуть явно, мой код вызова выглядит так this.actions$.ofType(SearchActions.SEARCH_MULTIPLE_NEW_QUERY).map(toPayload).fnWithMultipleAsyncReturns()
ishandutta2007

@ ishandutta2007 Привет. Вам лучше создать новый вопрос о вашей проблеме.
Мэтт

@Matt: создано, на случай, если вы хотите взглянуть ( stackoverflow.com/questions/43381922/… )
ishandutta2007

7

Если вы хотите предварительно подписаться на тот же Observable, который будет возвращен, просто используйте

.делать():

function getValueFromObservable() {
    return this.store.do(
        (data:any) => {
            console.log("Line 1: " +data);
        }
    );
}

getValueFromObservable().subscribe(
        (data:any) => {
            console.log("Line 2: " +data)
        }
    );

3
Вы также можете использовать другие операторы, например, .map(data => data)который делает то же самое, а затем подписаться на него везде, где вы ожидаете результата
ashok_khuman

Я согласен с ashok_khuman. Вот руководство angular.io/guide/pipes
Армандо Переа

Это может быть хороший ответ, но на самом деле то, что вы ничего об этом не объяснили, делает его плохим ответом. Что означает «предварительная подписка»? И должно ли это решить вопрос из открывателя веток?
Флориан Лейтгеб

обратите внимание, что в RxJS doтеперь вызывается 6 , tapи вы должны использовать его в конвейере. Также обратите внимание, что tapпринимает несколько параметров для разных обработчиков, таких как next, completeи error.
Simon_Weaver

7

Проблема в том, что данные фиксируются внутри наблюдаемого, и я могу просто записать их в консоль. Я хочу вернуть это значение и console.log или что-то еще из другого файла, вызвав функцию, в которой он находится.

Похоже, вы ищете геттер "текущего значения" внутри наблюдаемого, когда он испускает и после него.

Subjectи Observableне имеет такого. Когда значение испускается, оно передается его подписчикам иObservable ним завершается.

Вы можете использовать BehaviorSubject который сохраняет последнее переданное значение и немедленно отправляет его новым подписчикам.

У него также есть getValue()метод получения текущего значения;

Дальнейшее чтение:

RxJS BehaviorSubject

Как получить текущее значение RxJS Subject или Observable?


2

Наблюдаемые значения могут быть получены из любого места. Исходная последовательность сначала передается специальному наблюдателю, который может излучать где-то еще. Это достигается с помощью класса Subject из Reactive Extensions (RxJS).

var subject = new Rx.AsyncSubject();  // store-last-value method

Сохраните значение на наблюдателе .

subject.next(value); // store value
subject.complete(); // publish only when sequence is completed

Чтобы получить значение из другого места, подпишитесь на наблюдателя следующим образом:

subject.subscribe({
  next: (response) => {
      //do stuff. The property name "response" references the value
  }
});

Субъектами являются и Наблюдатели, и Наблюдатели. Есть и другие типы тем такие как BehaviourSubject и ReplaySubject, для других сценариев использования.

Не забудьте импортировать RxJS.

var Rx = require('rxjs');

1

Хотя предыдущие ответы могут работать в некотором роде, я думаю, что использование BehaviorSubject - это правильный способ, если вы хотите продолжать использовать наблюдаемые.

Пример:

    this.store.subscribe(
        (data:any) => {
            myService.myBehaviorSubject.next(data)
        }
    )

В Сервисе:

let myBehaviorSubject = new BehaviorSubjet(value);

В component.ts:

this.myService.myBehaviorSubject.subscribe(data => this.myData = data)

Надеюсь, это поможет!


0

Например, это мой шаблон html:

<select class="custom-select d-block w-100" id="genre" name="genre"
                  [(ngModel)]="film.genre"
                  #genreInput="ngModel"
                  required>
            <option value="">Choose...</option>
            <option *ngFor="let genre of genres;" [value]="genre.value">{{genre.name}}</option>
          </select>

Это поле связано с шаблоном из моего компонента:

  // Genres of films like action or drama that will populate dropdown list.
  genres: Genre[];

Я получаю с сервера жанры фильмов динамически. Для связи с сервером я создалFilmService

Это метод связи с сервером:

 fetchGenres(): Observable<Genre[]> {
    return this.client.get(WebUtils.RESOURCE_HOST_API + 'film' + '/genre') as Observable<Genre[]>;
  }

Почему этот метод возвращает Observable<Genre[]>не что-то вроде Genre[]?

JavaScript есть, asyncи он не ждет, пока метод вернет значение после дорогостоящего процесса. Под дорогими я подразумеваю процесс, который требует времени, чтобы вернуть значение. Как получение данных с сервера. Поэтому вам нужно вернуть ссылку на Observable и подписаться на нее.

Например, в моем компоненте:

ngOnInit() {
    this.filmService.fetchGenres().subscribe(
      val => this.genres = val
    );
  }

0
function getValueFromObservable() {
    this.store.subscribe(
        (data:any) => {
            return data
        }
    )
}
console.log(getValueFromObservable())

В приведенном выше случае console.log запускается до того, как обещание будет разрешено, поэтому значение не отображается, измените его на следующее

function getValueFromObservable() {
    return this.store
}

getValueFromObservable()
 .subscribe((data: any) => {
    // do something here with data
    console.log(data);
});

другое решение - когда вам нужны данные внутри getValueFromObservable для возврата наблюдаемого с помощью оператора и подписки на функцию.

 function getValueFromObservable() {
        return this.store.subscribe((data: any) => {
            // do something with data here
            console.log(data);
            //return again observable.
            return of(data);
       })
    }

    getValueFromObservable()
     .subscribe((data: any) => {
        // do something here with data
        console.log(data);
    });

0

В однопоточном, асинхронном, ориентированном на обещание, реактивном мире javascript async/await- лучший друг программиста императивного стиля:

(async()=>{

    const store = of("someValue");
    function getValueFromObservable () {
        return store.toPromise();
    }
    console.log(await getValueFromObservable())

})();

А если storeэто последовательность из нескольких значений:

  const aiFrom = require('ix/asynciterable').from;
  (async function() {

     const store = from(["someValue","someOtherValue"]);
     function getValuesFromObservable () {
        return aiFrom(store);
     }
     for await (let num of getValuesFromObservable()) {
       console.log(num);
     }
  })();

Зачем мне использовать асинхронность в случае массива данных?
Янош Винселлер,

@JanosVinceller Это просто пример, чтобы сосредоточиться на других частях кода. Вы можете заменить from(["someValue","someOtherValue"])наблюдаемое по вашему выбору, которое испускает более одного значения. Думаю, это могло быть более подходящим для использования interval(1000).
Маринос Ан,

0

Достойным способом было бы вернуть наблюдаемый объект из функции и подписаться на него везде, где это необходимо, поскольку наблюдаемые объекты ленивы, они начнут выдавать значения только тогда, когда они подписаны.

Здесь у меня есть еще одно интересное решение, управляемое событиями, с которым я изначально экспериментировал. В следующем примере это делается с помощью модуля « событий » nodejs. Вы можете использовать его с другими фреймворками, где существует аналогичный модуль ( Примечание : синтаксис и стиль могут изменяться в зависимости от используемого модуля).

var from =require("rxjs").from;
var map = require("rxjs/operators").map;
var EventEmitter = require("events");

function process(event) {
    from([1,2,3]).pipe(
        map(val => `The number is:: ${val}`)
    ).subscribe((data) => {
       event.emit("Event1", data); //emit value received in subscribe to the "Event1" listener
    });
}

function main() {
   class Emitter extends EventEmitter{};
    var event = new Emitter(); //creating an event
    event.on("Event1", (data)=>{ //listening to the event of name "Event1" and callback to log returned result
        console.log(data); //here log, print, play with the data you receive
    });
    process(event); //pass the event to the function which returns observable.
}

main(); //invoke main function

Это всего лишь пример, демонстрирующий идею, в которой мы можем передавать данные из разных мест методом испускания и прослушивания. Это также известно как код, управляемый событиями.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.