如何在 Angular2 loop/map 中执行异步 HTTP 请求并修改原始循环数组?
How to do a async HTTP request inside an Angular2 loop/map and modify the original loop array?
这里是 Angular2 的新手,想知道如何对数组模式执行此异步请求(不太确定模式名称)。
假设我使用 Angular2 的 Http
获取一个 YouTube 播放列表,其中每个 return 项都包含 videoId
。然后我需要使用 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.
});
});
});
所以上面的代码确实有效。但是我还有这两个问题:
如何将 videoData
连接回 data.items
并确保 data.items
中的正确项目与正确的 videoData
连接(使用 data.items
中相关项的 entry.id.videoId
的 videoData
)并创建新的 newArray
?
我怎么知道什么时候一切都完成了,并在所有异步请求完成后根据所有数据做些什么?
newArray 与第一个 HTTP 请求保持相同的顺序。作为第一个 HTTP,按 viewCount 排序进入 YouTube 搜索列表 API。像上面那样简单地嵌套 observable 不会保持原来的顺序。
更新
有了inoabrian solution,我就可以顺利达到以上三点。但是当我再次调用 myvideoservice 时(比如将 url 更改为新的 NextPageToken),主题 - this._videoObject 有 2 个观察者。再次加载,它有 3 个观察者,依此类推。我需要一种方法来将主题重置为只有 1 个观察者,这样我就不会有重复的视频。这是 clear/reset 主题的最佳实践方式吗?
您已经可以在 .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.
});
});
});
// 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 Sort -
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
假设只需要修改后的数据,一旦所有子请求完成
这就是您所需要的
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';
希望有帮助,虽然没有测试:)
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
字段中的视频数据合并(您的第一个问题)
source
当一切都完成时完成(你的第二个问题)
可以通过设置maxConcurrentRequest
来控制最大并发请求数为detailUrl
。当 maxConcurrentRequest
= 1 时,source
将按照播放列表中的相同顺序发出项目。如果你不在乎顺序,增加maxConcurrentRequest
以获得更快的速度
这里是 Angular2 的新手,想知道如何对数组模式执行此异步请求(不太确定模式名称)。
假设我使用 Angular2 的 Http
获取一个 YouTube 播放列表,其中每个 return 项都包含 videoId
。然后我需要使用 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.
});
});
});
所以上面的代码确实有效。但是我还有这两个问题:
如何将
videoData
连接回data.items
并确保data.items
中的正确项目与正确的videoData
连接(使用data.items
中相关项的entry.id.videoId
的videoData
)并创建新的newArray
?我怎么知道什么时候一切都完成了,并在所有异步请求完成后根据所有数据做些什么?
newArray 与第一个 HTTP 请求保持相同的顺序。作为第一个 HTTP,按 viewCount 排序进入 YouTube 搜索列表 API。像上面那样简单地嵌套 observable 不会保持原来的顺序。
更新
有了inoabrian solution,我就可以顺利达到以上三点。但是当我再次调用 myvideoservice 时(比如将 url 更改为新的 NextPageToken),主题 - this._videoObject 有 2 个观察者。再次加载,它有 3 个观察者,依此类推。我需要一种方法来将主题重置为只有 1 个观察者,这样我就不会有重复的视频。这是 clear/reset 主题的最佳实践方式吗?
您已经可以在 .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.
});
});
});
// 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 Sort - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
假设只需要修改后的数据,一旦所有子请求完成
这就是您所需要的
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';
希望有帮助,虽然没有测试:)
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
字段中的视频数据合并(您的第一个问题)
source
当一切都完成时完成(你的第二个问题)
可以通过设置maxConcurrentRequest
来控制最大并发请求数为detailUrl
。当 maxConcurrentRequest
= 1 时,source
将按照播放列表中的相同顺序发出项目。如果你不在乎顺序,增加maxConcurrentRequest
以获得更快的速度