5

这里是 Angular2 的新手,想知道如何对数组模式执行此异步请求(不太确定模式名称)。

因此,假设我使用 Angular2Http获取包含videoId在每个返回项目中的 YouTube 播放列表。然后,我需要使用 循环浏览此项目,videoId并向 YouTube 发出另一个请求以获取各个视频信息。这可以是另一个常见的 HTTP 请求,获取列表然后遍历列表并获取每个单独项目的详细信息。

let url = [YouTube API V3 list url]
let detailUrl = 'https://www.googleapis.com/youtube/v3/videos?part=contentDetails,statistics';

this.http.get(url)
    .map(res => res.json())
    .subscribe(data => {
        let newArray = data.items.map(entry => {

            let videoUrl = detailUrl + '&id=' + entry.id.videoId + 
                                       '&key=' + this.googleToken;

            this.http.get(videoUrl)
                .map(videoRes => videoRes.json())
                .subscribe(videoData => {
                     console.log(videoData);

                     //Here how to I join videoData into each item inside of data.items. 
                });
    });
});

所以上面的代码确实有效。但我仍然有这两个问题:

  1. 我如何加入videoData后面data.items并确保里面的正确项目data.items与正确videoData的(videoData使用entry.id.videoId里面相关项目的data.items)连接并创建新的newArray

  2. 在所有异步请求完成后,我如何知道一切何时完成并根据所有数据执行某些操作?

  3. newArray 保持与第一个 HTTP 请求相同的顺序。作为第一个按 viewCount 排序的 HTTP 访问 YouTube 搜索列表 API。像上面这样简单地嵌套 observable 不会保持原始顺序。

更新

使用inoabrian解决方案,我可以成功实现以上三点。但是当我再次调用 myvideoservice(比如将 url 更改为新的 NextPageToken)时,Subject - this._videoObject 有 2 个观察者。再次加载,它有 3 个观察者,依此类推。我需要一种方法将主题重置为只有 1 个观察者,这样我就不会有重复的视频。这是清除/重置主题的最佳实践方式吗?

4

4 回答 4

2
// You need to set up a subject in your sevice
private _videoObject = new Subject < any > ();
videoDataAnnounced$ = this._videoObject;


let url = [YouTube API V3 list url]
let detailUrl = 'https://www.googleapis.com/youtube/v3/videos?part=contentDetails,statistics';

this.http.get(url)
    .map(res => res.json())
    .subscribe(data => {
        this.cache = data.items;
        data.items.forEach(entry => {
            let videoUrl = detailUrl + '&id=' + entry.id.videoId + '&key=' + this.googleToken;
            this.http.get(videoUrl)
                .map(videoRes => videoRes.json())
                .subscribe(videoData => {
                    // This will announce that the full videoData is loaded to what ever component is listening.
                    this._videoObject.next(videoData);
                });
        });
    });



// In your constructor for your component you can subscribe to wait on the data being loaded.
public newVideos: any[] = [];
constructor(private myServiceToLoadVideos: myvideoservice) {
    let self = this;
    this.myServiceToLoadVideos.videoDataAnnounced$.subscribe((video) => {
        // Here you could loop through that
        self.myServiceToLoadVideos.cache.map(d => {
            if (d.id == video.id) {
                // Here you have the old data set without the details which is (d) from the cached data in the service.
                // And now you can create a new object with both sets of data with the video object
                d.snippet = video.items[0].snippet;
                d.statistics = video.items[0].statistics;
                d.contentDetails = video.items[0].contentDetails;
                this.posts = this.posts.concat(d);
            }
        });
        if(this.posts.length == this.cache.length){
          this.posts.sort(function(l,r){
            // if l is < r then it will return a negative number
            // else it will return a positive number.
            // That is how this sort function works
            return l.viewCount - r.viewCount;
          });

          // Here you will have all of the data and it will be sorted in order.
        }
    });
}

更新 - - -

The Subject is an observable - http: //reactivex.io/documentation/subject.html

    When you listen to the Subject it is not like q.all it will complete one by one until they complete.

The cache in the service call is just to keep track of the playlist.
Then when you receive the details in the constructor you can match them back up and keep order with the cache.

Q: "And when subscribe to the subject as in your code, the result is when ALL the "
next " in _videoObject finished, right?"
A: No, when each individual finishes you will get once at a time.

JS 排序 - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort

于 2016-07-02T05:52:54.057 回答
1

假设你只需要修改后的数据,一旦所有子请求完成

这是你需要的

this.http.get(url) // original request
      .map(res => res.json())  // extract json from response
      .switchMap(data => {  // take-over the original data and make the subsribers wait for sub-requests to complete

        let modifiedRequest = new Subject(); // observable to to publish modified data, once all sub-requests are completed

        let allSubRequests = data.items.map(entry => {  // array of sub-requests

          let videoUrl = detailUrl + '&id=' + entry.id.videoId +
                         '&key=' + this.googleToken;

          return this.http.get(videoUrl)
            .map(videoRes => videoRes.json())  // extract json for every sub-request
            .map(videoJson => {  // modify the original 'data' object with the data in videoJson (I don't know the format, so can't tell how-to)

              // modify your "data" here

              return videoJson; // let the flow continue
            });
        });

        let mergedSubRequests = Observable.range(0, 1).merge.apply(null, allSubRequests);  // merge all sub-requests to know when will all are completed

        mergedSubRequests.subscribe(  // subscribe to merged observable
          (d) => {}, // data from every sub-request, we don't need it
          (e) => {}, // error from every sub-request, handle it as you want
          () => {   // success, called after all sub-requests are completed
              modifiedRequest.next(data);  // send out the modified data
              modifiedRequest.complete();  // complete the temprary observable
              }
        );

        return modifiedRequest; // provide the observable with modified data, to subscribers of orginal request, they will not know the difference
      });

所需进口:

import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/merge';
import 'rxjs/add/observable/range';
import 'rxjs/add/operator/switchMap';

希望它有所帮助,但未经测试:)

于 2016-07-02T07:20:13.110 回答
1
let addDetail = (entry) => {
  let videoUrl = detailUrl + '&id=' + entry.id.videoId + 
                '&key=' + this.googleToken;

  return this.http.get(videoUrl)
    .map(videoRes => Object.assign({videoData: videoRes.json()}, entry)
};

let maxConcurrentRequest = 1;
let source = this.http.get(url)
  // any of mergeMap, concatMap, switchMap... will do
  .mergeMap(res => Observable.from(res.json().items))
  .mergeMap(addDetail, null, maxConcurrentRequest);
source.subscribe( /* Do anything you wish */ );

现在source发出的每个项目都是播放列表中的一个条目,与其在字段中的 videoData 合并videoData(你的第一个问题)

source一切都完成后完成(你的第二个问题)

您可以detailUrl通过设置来控制最大并发请求数maxConcurrentRequestmaxConcurrentRequest如果= 1,将按照与source播放列表中相同的顺序发出项目。如果您不关心订单,请增加maxConcurrentRequest以获得更快的速度

于 2016-07-02T08:05:57.797 回答
0

您已经可以访问该.map函数,entry您应该能够使用点符号在其上添加另一个值。所以做这样的事情:

let url = [YouTube API V3 list url]
let detailUrl = 'https://www.googleapis.com/youtube/v3/videos?part=contentDetails,statistics';

this.http.get(url)
    .map(res => res.json())
    .subscribe(data => {
        let newArray = data.items.map(entry => {

            let videoUrl = detailUrl + '&id=' + entry.id.videoId + 
                                       '&key=' + this.googleToken;

            this.http.get(videoUrl)
                .map(videoRes => videoRes.json())
                .subscribe(videoData => {
                     console.log(videoData);

                     entry.videoData = videoData;

                     //Here how to I join videoData into each item inside of data.items. 
                });
    });
});
于 2016-07-02T03:27:37.433 回答