Возврат наблюдаемого объекта внутреннего блока

Я хочу вернуть один объект из моей базы данных Firebase с наивысшим рейтингом качества за последние 7 дней. Я использую следующий код Typescript, чтобы, во-первых, запросить все потоки, созданные за последние семь дней, и, во-вторых, извлечь поток с самым высоким значением в quality. В идеале я могу просто «преобразовать» наблюдаемую results в окончательный повторный объект оператора forEach.

highestWeek: Number = 0;

...

getWeeksBest(): Observable<any> {
    let now = Date.now() / 1000;
    let results = this.af.database.list('/threads', {
        query: {
            orderByChild: 'created',
            startAt: now - 604800 //One week ago
        }
    })
    .map(results => {
        results.forEach(result => {
            if (result.quality > this.highestWeek) {
                console.log(result) //Logs next highest quality thread
                this.highestWeek = result.quality       
            }
        })
    })
    results.subscribe(results => console.log(results)) //Undefined
    return results //Undefined
}

person J. Adam Connor    schedule 31.01.2017    source источник
comment
В своем .map вы не преобразовываете результаты, вы создаете побочный эффект. Если вы можете немедленно преобразовать results, выполните преобразование в функции сопоставления и верните преобразованное значение, если вы хотите сделать что-то асинхронное, используйте flatMap - с помощью этого оператора вы можете сопоставить другие наблюдаемые.   -  person Balázs Édes    schedule 01.02.2017


Ответы (4)


Если вы хотите вернуть наблюдаемую, которая испускает поток с наивысшим качеством за последнюю неделю, функция, которую вы передаете оператору map, должна возвращать значение (как упоминалось в других ответах).

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

Внутренней подписки, вероятно, лучше избегать - вы будете делать дополнительную подписку с каждым звонком, и указанная подписка никогда не будет отменена. И было бы лучше избежать побочного эффекта, так как высочайшее качество будет доступно за счет эмитируемого потока.

Вместо этого вы можете сделать что-то вроде этого:

getWeeksBest(): Observable<any> {

    let now = Date.now() / 1000;
    let best = this.af.database.list('/threads', {
        query: {
            orderByChild: 'created',
            startAt: now - 604800 //One week ago
        }
    })

    // Filter out empty lists of threads, so that the resultant
    // observable emits nothing if there are no threads:

    .filter(threads => threads.length > 0)

    // Use Array.prototype.reduce to return the thread with the
    // highest quality:

    .map(threads => threads.reduce(
      (acc, thread) => thread.quality > acc.quality ? thread : acc
    ));
    return best;
}

Если вы хотите выдать null, если потоков нет, удалите filter и измените map на:

.map(threads => (threads.length === 0) ? null : threads.reduce(
  (acc, thread) => thread.quality > acc.quality ? thread : acc
))
person cartant    schedule 01.02.2017
comment
Это решение напрямую касается моего конкретного вопроса. Спасибо, @картант. - person J. Adam Connor; 01.02.2017
comment
Между сокращенными функциями и наблюдаемыми операторами, которые являются новыми для меня, я теряю след того, что происходит в моих функциях. Полагаю, мне было бы полезно сначала прочитать о сокращенных функциях. Хотя основной проблемой здесь было понимание того, как преобразовать FirebaseListObservable в объект. Что там происходит, для меня все еще довольно туманно. Знаете ли вы какие-либо сообщения в блогах, в которых подробно исследуется эта область? - person J. Adam Connor; 01.02.2017
comment
Кажется, это достаточно хорошо объясняет уменьшение массива . Для RxJS: Подробное описание RxJS; Введение в реактивное программирование; и Не отписываться. - person cartant; 01.02.2017
comment
Документация RxJS5 находится в стадии разработки, но есть много RxJS4. полезная документация. Критические изменения между версиями подробно описаны здесь. - person cartant; 01.02.2017

Таким образом, вопрос, вероятно, заключается в том, почему вы получаете undefined в обратном вызове subscribe(...) в переменной results.

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

.map(results => {
   results.forEach(result => {
       if (result.quality > this.highestWeek) {
           console.log(result) //Logs next highest quality thread
           this.highestWeek = result.quality       
       }
   });
   return results; 
})

В вашем случае я бы даже лучше использовал оператор .do() вместо .map().

В конце getWeeksBest() вы возвращаете Observable, поэтому последняя строка определенно не возвращает undefined.

getWeeksBest(): Observable<any> {
    let now = Date.now() / 1000;
    let results = this.af.database.list('/threads', {
        ...
    });
    return results;
}
person martin    schedule 31.01.2017

RxJS предоставляет множество операторов для преобразования данных. В вашем случае вы хотите получить объект с самым высоким значением quality, поэтому мы можем использовать оператор reduce, где мы применим функцию maxBy().

const weeks = [
    {
      quality: 1
    },
    {
      quality: 15
    },
    {
      quality: 5
    },
    {
      quality: 8
    }
  ];


//maxBy stateless function (you can see a currified function)
const maxBy = (prop) => (a, b) => a[prop] > b[prop] ? a : b;

//Fetch data and apply the maxBy when the reduce is perform over the collection
function fetchData() {
  //You can perform some async actions
  return Rx
    .Observable
    .from(weeks)
    .reduce(maxBy('quality'), 0);
}

fetchData().subscribe(x => console.log(x));

Не стесняйтесь проверить пример на Plunker

person Paul Boutes    schedule 01.02.2017
comment
Trypescript по какой-то причине жалуется на from, а import 'rxjs/add/operator/from'; этого не меняет. - person J. Adam Connor; 01.02.2017
comment
Обязательно импортируйте Observable, from и оператор reduce или весь rxJs. Вы можете проверить предыдущий пост, который я сделал здесь - person Paul Boutes; 01.02.2017
comment
Я импортировал Observable, но по какой-то причине не смог импортировать from, используя оператор в комментарии выше. - person J. Adam Connor; 01.02.2017

Мартин прав, вам нужно добавить оператор return. Но также имейте в виду, что Array.forEach всегда возвращает undefined. Итак, в вашем случае вы можете использовать Array.map.

Но в целом я бы предложил что-то вроде этого:

getAllThreads$(): Observable<any[]> {
  let now = Date.now() / 1000;
  return this.af.database.list('/threads', {
    query: {
        orderByChild: 'created',
        startAt: now - 604800 //One week ago
    }
  })
}

getWeeksBest$():Observable<number>{
  return this.getAllThreads$().map(threads => Array.isArray(threads )? threads.reduce((highestQuality, thread) => {
   let currentQuality = thread? thread.quality: 0;
   return currentQuality > highestQuality? currentQuality: highestQuality;
  }, 0): 0);
}

и где-то в вашем конструкторе классов просто подпишитесь на getWeeksBest$

person cyr_x    schedule 31.01.2017