简体   繁体   English

限制rxjs进行的http调用

[英]Rate limiting http calls made by rxjs

I am writing a service where people can paste in urls from Spotify playlists and then export the playlist in a different service. 我正在编写一项服务,人们可以从Spotify播放列表粘贴网址,然后在不同的服务中导出播放列表。 For each track url that is pasted in a request needs to be made to the Spotify api. 对于每个跟踪需要在Spotify api中进行请求粘贴的URL。

This code: 这段代码:

        Rx.Observable.fromArray<ITrackIdentifier>( this._allTracks )
            .pluck<string>( "id" )
            .distinct()
            .flatMap(
                ( trackId ) => this.spotifyService.lookupTrack( trackId ).
                    catch( ( error ) => this.handleError( error ) ))
            .subscribe(
                ( result ) => this.handleTrackLookupResult( result ),
                ( error ) => this.handleError( error ),
                () => this.handleComplete()
            );
  1. Creates an observable from a list of ITrackIdentifiers 从ITrackIdentifiers列表中创建一个observable
  2. takes the id of the track identifier to create an observable of strings (ids) 获取轨道标识符的id以创建可观察的字符串(ID)
  3. Removes any duplicate id's in the list 删除列表中的任何重复ID
  4. creates an observable for each http call made to spotify (and catches errors) 为每个http spot调用创建一个observable(并捕获错误)
  5. merges results from all these observables into one stream with flatmap 将所有这些可观察对象的结果合并到一个带有flatmap的流中

This actually works fine except for when a large number of tracks are added. 除了添加大量曲目之外,这实际上工作正常。 One of my sample playlists has over 500 tracks so immediately 500 calls are made and the browser needs to deal with them / return items from the cache so the browser is slow and locks up AND spotify returns loads of errors as I exceed the api call limit. 我的一个示例播放列表有超过500个曲目,因此立即进行500次调用,浏览器需要处理它们/从缓存中返回项目,因此浏览器速度很慢并且锁定并且当我超过api调用限制时,spotify会返回错误负载。

I want to be able to only have say 10 calls running at the same time. 我希望能够只说10个同时运行的呼叫。 Merge with maxConcurrent set seems like the perfect solution as discussed on Stackoverflow . 与maxConcurrent合并组似乎是完美的解决方案上所讨论#1

This would look like this: 这看起来像这样:

        Rx.Observable.fromArray<ITrackIdentifier>( this._allTracks )
            .pluck<string>( "id" )
            .distinct()
            .map(
                ( trackId ) => this.spotifyService.lookupTrack( trackId ).
                    catch( ( error ) => this.handleError( error ) ))
            .merge(10)
            .subscribe(
                ( result ) => this.handleTrackLookupResult( result ),
                ( error ) => this.handleError( error ),
                () => this.handleComplete()
            );

But it just doesn't work. 但它只是不起作用。 In the chrome network debugger you can see all the calls made at the same time and most queueing for ages until they fail. 在Chrome网络调试器中,您可以看到所有呼叫同时进行,并且大多数排队等待多年,直到它们失败。

Why isn't this working? 为什么这不起作用? How else can I get round this issue? 我怎么能解决这个问题呢?

Here is the Github checkin with the project at this stage: 以下是Github在此阶段与项目签到的签到

The problem with your code using merge is that the spotifyService.lookupTrack doesn't return an Observable but a Promise . 使用merge的代码问题是spotifyService.lookupTrack不返回Observable而是Promise Some of Observable s functions like flatMap will handle Promise s as well , but the difference between an Observable and a Promise is that the Observable is lazy, while the Promise is not. flatMap这样的一些Observable函数也会处理Promise ,但是ObservablePromise之间的区别在于Observable是懒惰的,而Promise则不是。 You can make a lazy observable from a promise factory function using Observable.defer , as user3743222 suggests. 您可以使用Observable.defer从promise工厂函数中创建一个惰性observable,如user3743222所示。 This little example is in JavaScript instead of TypeScript so it can be run here. 这个小例子是用JavaScript而不是TypeScript,因此它可以在这里运行。

 console.log = x => {var d = document,b=d.body,p=d.createElement('pre'); p.style.margin = "0"; p.appendChild(d.createTextNode(''+x)); b.appendChild(p); window.scrollTo(0, b.scrollHeight); }; function log_delay(timeout, value) { return new Promise(resolve => { console.log('Start: ' + value); setTimeout(() => { console.log('End: ' + value); resolve(value); }, timeout); }); } Rx.Observable.range(0, 6) .map(x => Rx.Observable.defer( () => log_delay(1000, x) .catch(e => console.log('Inner catch')) )) .merge(2) .subscribe( s => console.log('Result: ' + s), s => console.log('Error: ' + s), s => console.log('Complete') ); 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.0.7/rx.all.js"></script> 

I managed to get it working somewhat how I wanted but I am still curious as to why merge didn't work. 我设法让它按照我想要的方式工作,但我仍然很好奇为什么合并不起作用。 Here the list of unique ids is built and then we use concatMap to create an Observable for each id and then wait for a delay before moving onto the next item: 这里构建了唯一ID列表,然后我们使用concatMap为每个id创建一个Observable,然后在移动到下一个项目之前等待一段时间:

        Rx.Observable.fromArray<ITrackIdentifier>( this._allTracks )
            .pluck<string>( "id" )
            .distinct()
            .concatMap( ( id, index ) => Rx.Observable.interval( 50 ).take( 1 ).map( () => { return id } ) )
            .flatMap(
                ( trackId ) => this.spotifyService.lookupTrack( trackId ).
                catch( ( error ) => this.handleError( error ) ))
            .subscribe(
                ( result ) => this.handleTrackLookupResult( result ),
                ( error ) => this.handleError( error ),
                () => this.handleComplete()
            );

In this example I wait 50ms between each call. 在这个例子中,我在每次通话之间等待50ms。 This reduces the errors a lot. 这大大减少了错误。

Here is the Github checkin with the project at this stage. 这是Github在此阶段与项目签到的签到

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM