Angular 循环 HTTP 请求将值映射到响应

Posted

技术标签:

【中文标题】Angular 循环 HTTP 请求将值映射到响应【英文标题】:Angular Looped HTTP Request Map Value to Responses 【发布时间】:2017-10-27 07:19:08 【问题描述】:

在我的服务类中,我正在循环一个 http get 请求并使用 rxjs forkJoin 将所有响应组合成一个 observable,我将它返回到我的组件。对于每个返回的响应,我需要向 json 添加两个属性(readySystem 是一个对象,serviceType 是一个字符串)。对于循环的每次迭代,它们中的每一个的值都是不同的。

如何保留/存储/保留 and 的值并将它们映射/添加到正确的响应中?

按照我在下面尝试的方式,在最终 observable 中返回的每个响应中,两者的值都是相同的。

  getServices() 

  for (var x = 0; x < this.service.items.length; x++ )
        var num = Object.keys(this.service.items[x].links).length;

         for (var key in this.service.items[x].links) 
            var systemName = this.service.items[x].systemName;
            var environment = this.service.items[x].environment;
            var server = this.service.items[x].server;
            var port = this.service.items[x].port;
            var linkName = this.service.items[x].links[key];
            var serviceType = key;
            this.observables.push(
            this.http.get('http://localhost:3000/myapi/get/service?server=' + server + '&service=' + linkName)
            .map((res:Response) =>  
                var output = res.json()
                for (var obj in output) 
                    if (output.hasOwnProperty(obj))

                        var readySystem = new System(systemName,
                         environment,
                         server,
                         port,
                         linkName);

                    output[obj].System = readySystem;
                    output[obj].serviceType = serviceType;
                    
                
                return output;
        )
            );
        
        ;
        return Observable.forkJoin(this.observables);
;

更新:根据下面答案中提供的建议代码更改,我得到如下输出:

0: Array(33)
1: System
    systemName: "my system"
    environment: "my environment"
    etc.
2: "myservice"
3: Array(35)
4: System
   etc.
5: "myotherservice"

但是,需要的是:

0: Array(33)
 0: Object
  > System
      systemName: "my system"
      environment: "my environment"
      etc.
   serviceType: "myservice"
 1: Object
  > System
      systemName: "my system"
      environment: "my environment"
      etc.
   serviceType: "myotherservice"
 etc.
1: Array(35)
 0: Object

【问题讨论】:

【参考方案1】:

这可能是一个简单的经典 JS 闭包问题。当所有异步调用(http.gets 和 Observable.forkJoin)都解决后,所有作用域为vars 的函数(xnumkeysystemNameenvironmentserverport、...等)在for 循环中设置为他们的最后一个值。最简单的解决方案是使用 ES6 块作用域 letconst 声明。

所以尝试将您的代码更改为:

for (let x = 0; x < this.service.items.length; x++ )
  let num = Object.keys(this.service.items[x].links).length;

  for (let key in this.service.items[x].links) 
    let systemName = this.service.items[x].systemName;
    let environment = this.service.items[x].environment;
    let server = this.service.items[x].server;
    let port = this.service.items[x].port;
    let linkName = this.service.items[x].links[key];
    let serviceType = key;

    let url = `http://localhost:3000/myapi/get/service?server=$server&service=$linkName`;
    this.observables.push(this.http.get(url)
      .map((res:Response) =>  
        let output = res.json();
        for (let obj in output) 
          if (output.hasOwnProperty(obj)) 

            let readySystem = new System(systemName,
              environment,
              server,
              port,
              linkName
            );

            output[obj].System = readySystem;
            output[obj].serviceType = serviceType;
          
        
        return output;
      
    ));
  ;
  return Observable.forkJoin(this.observables);
;

一般来说,在使用 Angular 2/4 编码时,没有理由不使用 new ES6 features。

您还可以使用Array.forEach() 函数重写您的第一个for 循环(我假设this.service.items 是一个数组,因为它的length 属性):

this.service.items.forEach(item => 
  let num = Object.keys(item.links).length;

  for (let key in item.links) 
    let systemName = item.systemName;
    let environment = item.environment;
    let server = item.server;
    let port = item.port;
    let linkName = item.links[key];
    let serviceType = key;

    let url = `http://localhost:3000/myapi/get/service?server=$server&service=$linkName`;
    this.observables.push(this.http.get(url)
      .map((res:Response) =>  
        let output = res.json();
        for (let obj in output) 
          if (output.hasOwnProperty(obj)) 

            let readySystem = new System(systemName,
              environment,
              server,
              port,
              linkName
            );

            output[obj].System = readySystem;
            output[obj].serviceType = serviceType;
          
        
        return output;
      
    ));
  ;
  return Observable.forkJoin(this.observables);
);

【讨论】:

【参考方案2】:

这是一个闭包问题。根据您的原始代码:

for (var key in this.service.items[x].links) 
    var systemName = this.service.items[x].systemName;

在这里,您可能认为每次执行块时都在创建一个新的systemName 变量。实际上,该变量只创建一次,并且每次执行块时才为其分配新值。区别很重要,因为您在下面进行了异步调用http.get

this.observables.push(
          this.http.get('http://localhost:3000/myapi/get/service?server=' + server + '&service=' + linkName)
            .map(res =>  
              var output = res.json()
              for (var obj in output) 
                if (output.hasOwnProperty(obj))
                  var readySystem = new System(systemName,

systemName 具有正确的值时,map 内的回调不会立即在循环内执行。它只是存储在内存中以供以后执行(当http.get 返回结果时)。回调的存储体不携带systemName 值,只是对变量的引用。

现在,每次循环执行时,它都会为systemName 分配一个新值并存储一个回调。到循环结束时,systemName 的值是循环分配的最后一个值。

一段时间后,http.get 请求开始完成,存储的回调被执行,它们读取systemName,它们都得到相同的最后一个值。这就是你正在经历的。

最简单的解决方法实际上是为每个块执行创建一个新的systemName 变量。这可以通过使用let 而不是var 来实现。因此,只需对所有相关变量使用 let 即可解决问题:

let systemName = this.service.items[x].systemName;
let environment = this.service.items[x].environment;
let server = this.service.items[x].server;
let port = this.service.items[x].port;
let linkName = this.service.items[x].links[key];
let serviceType = key;

【讨论】:

【参考方案3】:

只需使用 Observable.of 将这些值添加到 fork

function getObsevrables(observables, readySystem, serviceType) 
   return Rx.Observable.forkJoin([
     Rx.Observable.of(readySystem), 
     Rx.Observable.of(serviceType), 
     ...observables
    ]);


getObsevrables([Rx.Observable.of(1), Rx.Observable.of(2)], 
           ready: true, 'type1')
   .subscribe(x=>console.log(x))

更新:

我试图更新你的代码。

getServices() 

for (var x = 0; x < this.service.items.length; x++) 
  var num = Object.keys(this.service.items[x].links).length;

  for (var key in this.service.items[x].links) 
    var systemName = this.service.items[x].systemName;
    var environment = this.service.items[x].environment;
    var server = this.service.items[x].server;
    var port = this.service.items[x].port;
    var linkName = this.service.items[x].links[key];
    var serviceType = key;

    var readySystem = new System(systemName,
      environment,
      server,
      port,
      linkName);

   // getObservables([Observable.of(1), Observable.of(2)], readySystem, serviceType).subscribe(x => console.log(x));

    this.observables.push(
      Observable.combineLatest(
        this.http.get('http://localhost:3000/myapi/get/service?server=' + server + '&service=' + linkName)
        .map((res: Response) => 
          var output = res.json();
          return output;
        );
       , 
       Observable.of(serviceType), 
       Observable.of(readySystem)
    )
   );


【讨论】:

谢谢。我了解该函数在做什么,但 ready: true 的目的是什么?我应该何时何地调用该函数? 在一个循环中,您创建了一个可观察的数组,它们将执行 http 调用,据我所知,您为每个 forkJoin 创建了两个变量 - readySystem 和 serviceType。因此,您只需使用 Observable.of 将它们添加到可观察数组中,然后执行与现在相同的返回。 我的 forkJoin 发生在我的内部和外部循环之外的 return 语句中。我相信我需要将内部循环中的两个变量添加到每个单独的 observable 中,然后再将它们推送到我的 observable 数组中。 当然。我的观点是您可以将通过 http 接收的 forkJoin 未来数据和现有数据与 Observable.of 混合在一起 当我在我的 observables 数组上调用 push 之前添加带有 console.log 的函数调用时,我可以确认我看到需要捕获的值正在输出到控制台。但是,我不明白为什么我仍然看不到如何正确地将值与添加到数组中的所有其他内容连接起来。我的最终输出仍然没有这两个字段。我会尽快粘贴我当前的更改。

以上是关于Angular 循环 HTTP 请求将值映射到响应的主要内容,如果未能解决你的问题,请参考以下文章

如何在Word中将http请求响应映射到我定义的对象

Gatling 2.0.3 - 将值映射到模板

Angular 11异步验证器触发真实响应的无限循环请求

无法获得在 Angular 2 代码中映射的正确 http 响应

Angular 2:无法访问 http 请求中的响应标头

如何将值从节点返回到原点(angular2 请求)