Используя Http, мы вызываем метод, который выполняет сетевой вызов и возвращает наблюдаемый http:
getCustomer() {
return this.http.get('/someUrl').map(res => res.json());
}
Если мы возьмем это наблюдаемое и добавим к нему несколько подписчиков:
let network$ = getCustomer();
let subscriber1 = network$.subscribe(...);
let subscriber2 = network$.subscribe(...);
Что мы хотим сделать, так это убедиться, что это не вызывает многократных сетевых запросов.
Это может показаться необычным сценарием, но на самом деле он довольно распространен: например, если вызывающий абонент подписывается на наблюдаемое для отображения сообщения об ошибке и передает его в шаблон с помощью асинхронного канала, у нас уже есть два подписчика.
Как правильно сделать это в RxJs 5?
А именно, это, кажется, работает нормально:
getCustomer() {
return this.http.get('/someUrl').map(res => res.json()).share();
}
Но это идиоматический способ сделать это в RxJs 5, или мы должны вместо этого сделать что-то еще?
Примечание: согласно Angular 5 new HttpClient
, .map(res => res.json())
часть во всех примерах теперь бесполезна, так как результат JSON теперь принят по умолчанию.
источник
Ответы:
Кэшируйте данные и, если доступно, кешируйте, возвращайте это, иначе сделайте HTTP-запрос.
Пример плунжера
Эта статья https://blog.thoughtram.io/angular/2018/03/05/advanced-caching-with-rxjs.html является отличным объяснением того, как кэшировать
shareReplay
.источник
do()
в отличие отmap()
не изменить событие. Вы могли бы также использовать,map()
но тогда вы должны были убедиться, что правильное значение возвращается в конце обратного вызова..subscribe()
, не нужно значение, вы можете сделать это, потому что он может получить простоnull
(в зависимости от того, чтоthis.extractData
возвращает), но IMHO, это не очень хорошо выражает намерение кода.this.extraData
заканчивается как вextraData() { if(foo) { doSomething();}}
противном случае, возвращается результат последнего выражения, которое может быть не тем, что вы хотите.if (this.observable) { return this.observable; } else { this.observable = this.http.get(url) .map(res => res.json().data); return this.observable; }
Согласно предложению @Cristian, это один из способов, который хорошо работает для наблюдаемых HTTP, которые излучают только один раз, а затем завершают:
источник
share
оператор может быть разумным выбором (хотя и с некоторыми неприятными крайними случаями). ПодробноеpublishLast().refCount()
нельзя строго отменить исходную наблюдаемую заметку источника , но после отмены всех подписок на возвращаемую заметку наблюдаемогоrefCount
эффекта чистый источник - наблюдаемый источник будет отписан, если отменить его, если он находится "в полете"ОБНОВЛЕНИЕ: Бен Леш говорит, что в следующем небольшом выпуске после 5.2.0 вы сможете просто вызвать функцию shareReplay () для кеширования.
РАНЕЕ .....
Во-первых, не используйте share () или publishReplay (1) .refCount (), они одинаковы, и проблема с ним в том, что он разделяет, только если установлены соединения, когда наблюдаемая активна, если вы подключаетесь после ее завершения , он снова создает новый наблюдаемый, перевод, а не кеширование.
Birowski дал правильное решение выше, которое заключается в использовании ReplaySubject. ReplaySubject будет кэшировать значения, которые вы ему даете (bufferSize) в нашем случае 1. Он не создаст новую наблюдаемую, например, share (), когда refCount достигнет нуля, и вы создадите новое соединение, что является правильным поведением для кэширования.
Вот многоразовая функция
Вот как это использовать
Ниже приведена более продвинутая версия кешируемой функции. Эта позволяет иметь собственную таблицу поиска + возможность предоставить пользовательскую таблицу поиска. Таким образом, вам не нужно проверять this._cache, как в примере выше. Также обратите внимание, что вместо передачи наблюдаемой в качестве первого аргумента, вы передаете функцию, которая возвращает наблюдаемые, это потому, что Http Angular выполняет сразу, поэтому, возвращая лениво выполненную функцию, мы можем решить не вызывать ее, если она уже находится в наш кеш.
Использование:
источник
const data$ = this._http.get('url').pipe(cacheable()); /*1st subscribe*/ data$.subscribe(); /*2nd subscribe*/ data$.subscribe();
? Таким образом, он ведет себя больше как любой другой оператор ..В rxjs 5.4.0 появился новый метод shareReplay .
Автор прямо говорит, что «идеально подходит для обработки таких вещей, как кэширование результатов AJAX»
rxjs PR # 2443 feat (shareReplay): добавляет
shareReplay
вариантpublishReplay
источник
в соответствии с этой статьей
так внутри, если заявления просто добавить
в
.map(...)
источник
В версии 5.4.0 rxjs (2017-05-09) добавлена поддержка shareReplay .
Вы можете легко изменить угловой сервис, чтобы использовать его и возвращать наблюдаемое с кэшированным результатом, который только когда-либо сделает вызов http один раз (при условии, что первый вызов был успешным).
Пример угловой службы
Вот очень простое обслуживание клиентов, которое использует
shareReplay
.customer.service.ts
Обратите внимание, что присваивание в конструкторе может быть перемещено в метод,
getCustomers
но поскольку наблюдаемые, возвращаемые изHttpClient
"холодного", делать это в конструкторе допустимо, поскольку вызов http будет выполняться только каждый раз при первом вызовеsubscribe
.Также предполагается, что исходные возвращаемые данные не устаревают в течение времени жизни экземпляра приложения.
источник
Я пометил вопрос, но постараюсь попробовать.
Вот доказательство :)
Есть только один вынос:
getCustomer().subscribe(customer$)
Мы не подписываемся на ответ API
getCustomer()
, мы подписываемся на ReplaySubject, который является наблюдаемым, который также может подписаться на другой Observable и (и это важно) удерживать его последнее переданное значение и повторно опубликовать его на любом из его (ReplaySubject's ) подписчики.источник
Я нашел способ сохранить результат http get в sessionStorage и использовать его для сеанса, чтобы он никогда больше не вызывал сервер.
Я использовал его для вызова github API, чтобы избежать ограничения использования.
К вашему сведению, предел сессии - 5 млн. (Или 4,75 млн.). Таким образом, он не должен использоваться таким образом для большого набора данных.
------ edit -------------
Если вы хотите обновить данные с помощью F5, которая использует данные памяти вместо sessionStorage;
источник
sessionStorage
, я бы использовал их только для данных, которые, как ожидается, будут согласованы на протяжении всего сеанса.getCustomer
в своем примере. ;) Так что просто хотел предупредить некоторых ппл, которые могут не видеть рисков :)Реализация, которую вы выберете, будет зависеть от того, хотите ли вы отменить подписку (), чтобы отменить ваш HTTP-запрос или нет.
В любом случае, декораторы TypeScript - хороший способ стандартизировать поведение. Это тот, который я написал:
Определение декоратора:
источник
Property 'connect' does not exist on type '{}'.
с линииreturnValue.connect();
. Можете ли вы уточнить?Кэшируемые данные ответов HTTP с использованием Rxjs Observer / Observable + Caching + Subscription
Смотрите код ниже
* Отказ от ответственности: я новичок в rxjs, так что имейте в виду, что я могу неправильно использовать подход наблюдаемого / наблюдателя. Мое решение - это просто конгломерат других решений, которые я нашел, и является следствием того, что не удалось найти простое, хорошо документированное решение. Таким образом, я предоставляю свое полное решение для кода (как я хотел бы найти) в надежде, что оно поможет другим.
* обратите внимание, что этот подход основан на GoogleFirebaseObservables. К сожалению, мне не хватает надлежащего опыта / времени, чтобы повторить то, что они сделали под капотом. Но ниже приведен упрощенный способ предоставления асинхронного доступа к некоторым данным в кэше.
Ситуация : компоненту «список товаров» поручено отображение списка товаров. Сайт представляет собой одностраничное веб-приложение с некоторыми кнопками меню, которые будут «фильтровать» продукты, отображаемые на странице.
Решение : компонент «подписывается» на сервисный метод. Метод service возвращает массив объектов продукта, к которым компонент обращается через обратный вызов подписки. Сервисный метод оборачивает свою активность во вновь созданном Обозревателе и возвращает наблюдателя. Внутри этого наблюдателя он ищет кэшированные данные и передает их обратно подписчику (компоненту) и возвращает. В противном случае он выполняет http-вызов для извлечения данных, подписывается на ответ, где вы можете обработать эти данные (например, сопоставить данные с вашей собственной моделью), а затем передать данные обратно подписчику.
Код
продакт-list.component.ts
product.service.ts
product.ts (модель)
Вот пример вывода, который я вижу, когда загружаю страницу в Chrome. Обратите внимание, что при первоначальной загрузке продукты выбираются из http (обратитесь к моей службе отдыха узла, которая работает локально на порту 3000). Когда я нажимаю, чтобы перейти к «фильтрованному» представлению товаров, товары находятся в кеше.
Мой журнал Chrome (консоль):
... [нажал кнопку меню для фильтрации продуктов] ...
Вывод: это самый простой способ, который я нашел (на данный момент) для реализации кэшируемых данных ответов http. В моем угловом приложении каждый раз, когда я перехожу к другому виду продуктов, компонент списка продуктов перезагружается. ProductService, по-видимому, является общим экземпляром, поэтому локальный кэш «products: Product []» в ProductService сохраняется во время навигации, а последующие вызовы «GetProducts ()» возвращают кэшированное значение. В заключение я прочитал комментарии о том, как наблюдаемые / подписки должны быть закрыты, когда вы закончите, чтобы предотвратить «утечки памяти». Я не включил это здесь, но об этом нужно помнить.
источник
Я предполагаю, что @ ngx-cache / core может быть полезен для поддержки функций кэширования для http-вызовов, особенно если HTTP-вызов выполняется как на браузерной, так и на серверной платформах.
Допустим, у нас есть следующий метод:
Вы можете использовать
Cached
декоратор @ NGX-кэш / ядер для хранения возвращаемого значения из метода , делающего HTTP заходящих вcache storage
( может быть настраиваемыми, пожалуйста , проверьте реализацию на нг семечек / универсальные ) - справа на первое исполнении. В следующий раз, когда метод вызывается (независимо от браузера или серверной платформы), значение извлекается изstorage
cache storage
.Там также возможность использования методов кэширования (
has
,get
,set
) с помощью кэширования API .anyclass.ts
Вот список пакетов, как для кэширования на стороне клиента, так и на стороне сервера:
источник
rxjs 5.3.0
Я не был доволен
.map(myFunction).publishReplay(1).refCount()
С несколькими подписчиками,
.map()
выполняетсяmyFunction
дважды в некоторых случаях (я ожидаю, что он будет выполняться только один раз). Кажется, одно исправлениеpublishReplay(1).refCount().take(1)
Еще одна вещь, которую вы можете сделать, это просто не использовать
refCount()
и сразу же сделать Observable горячим:Это запустит HTTP-запрос независимо от подписчиков. Я не уверен, отменит ли отмена подписки до завершения HTTP GET или нет.
источник
Мой личный фаворит - использовать
async
методы для звонков, которые делают сетевые запросы. Сами методы не возвращают значение, вместо этого они обновляютBehaviorSubject
внутри одной и той же службы, на которую будут подписываться компоненты.Теперь зачем использовать
BehaviorSubject
вместоObservable
? Так как,onnext
.getValue()
метод.Пример:
customer.service.ts
Тогда, где требуется, мы можем просто подписаться на
customers$
.Или, может быть, вы хотите использовать его непосредственно в шаблоне
Так что теперь, пока вы не сделаете еще один вызов
getCustomers
, данные сохраняются вcustomers$
BehaviorSubject.Так что, если вы хотите обновить эти данные? просто позвоните
getCustomers()
Используя этот метод, нам не нужно явно сохранять данные между последующими сетевыми вызовами, поскольку они обрабатываются
BehaviorSubject
.PS: Обычно, когда компонент уничтожается, хорошей практикой является избавление от подписок, для этого вы можете использовать метод, предложенный в этом ответе.
источник
Отличные ответы.
Или вы могли бы сделать это:
Это из последней версии rxjs. Я использую 5.5.7 версию RxJS
источник
Просто вызовите share () после map и перед любой подпиской .
В моем случае у меня есть универсальный сервис (RestClientService.ts), который выполняет вызов rest, извлекает данные, проверяет на наличие ошибок и возвращает наблюдаемый в конкретную службу реализации (например, ContractClientService.ts), наконец, эту конкретную реализацию. возвращает наблюдаемый в de ContractComponent.ts, и этот подписывается, чтобы обновить представление.
RestClientService.ts:
ContractService.ts:
ContractComponent.ts:
источник
Я написал кеш-класс,
Это все статично из-за того, как мы его используем, но не стесняйтесь сделать его обычным классом и сервисом. Я не уверен, что Angular хранит один экземпляр за все время (новичок в Angular2).
И вот как я это использую:
Я предполагаю, что мог бы быть более умный способ, который использовал бы некоторые
Observable
уловки, но это было просто хорошо для моих целей.источник
Просто используйте этот слой кеша, он делает все, что вам нужно, и даже управляйте кешем для запросов ajax.
http://www.ravinderpayal.com/blogs/12Jan2017-Ajax-Cache-Mangement-Angular2-Service.html
Это очень легко использовать
Слой (как инъекционный угловой сервис)
источник
Это
.publishReplay(1).refCount();
или.publishLast().refCount();
так как Angular Http наблюдаемые завершены после запроса.Этот простой класс кэширует результат, поэтому вы можете подписаться на .value много раз и делает только 1 запрос. Вы также можете использовать .reload (), чтобы сделать новый запрос и опубликовать данные.
Вы можете использовать его как:
и источник:
источник
Вы можете создать простой класс Cacheable <>, который помогает управлять данными, полученными с http-сервера несколькими подписчиками:
использование
Объявите объект Cacheable <> (предположительно, как часть службы):
и обработчик:
Звонок из компонента:
Вы можете подписаться на несколько компонентов.
Более подробная информация и пример кода находятся здесь: http://devinstance.net/articles/20171021/rxjs-cacheable
источник
Вы можете просто использовать ngx-cacheable ! Это лучше подходит для вашего сценария.
Итак, ваш класс обслуживания будет что-то вроде этого -
Вот ссылка для получения дополнительной ссылки.
источник
Вы пробовали запустить код, который у вас уже есть?
Поскольку вы создаете Observable из обещания, полученного в результате
getJSON()
, сетевой запрос выполняется до того, как кто-либо подпишется. И полученное обещание разделяют все подписчики.источник