如何在Angular2循环/映射中执行异步HTTP请求并修改原始循环数组?

时间:2016-07-01 23:45:52

标签: asynchronous angular observable angular2-http

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

因此,我们假设我使用Angular2的Http来获取每个返回项目中包含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({{{}}相关联1}}正在使用videoData内相关项的entry.id.videoId并创建新的data.items

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

  3. newArray保持与第一个HTTP请求相同的顺序。当第一个HTTP按照viewCount的顺序点击YouTube搜索列表API。如上所述简单地嵌套可观察将不会保持原始顺序。

  4. 更新

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

4 个答案:

答案 0 :(得分: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.
        }
    });
}
  

UPDATE        - - -

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

答案 1 :(得分: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';

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

答案 2 :(得分: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来控制maxConcurrentRequest的最大并发请求数。 maxConcurrentRequest = 1时,source将以与播放列表中相同的顺序发出项目。如果您不关心订单,请提高maxConcurrentRequest以获得更快的速度

答案 3 :(得分: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. 
                });
    });
});