将多个WS调用组合到一个结果中时处理错误

时间:2012-09-30 23:59:27

标签: scala playframework-2.0

我正在学习scala,play和web services,所以请耐心等待。我已经建立了一个小型聚合服务,它结合了天气网络服务和谷歌的地理编码,并提供网络服务。我有一些工作但我对处理错误的正确方法感到困惑。 (我在帖子末尾发布了代码)

所以这些地方api使用lat / long,所以我使用geocode api从邮政编码获得lat / long。处理来自对地理编码api的调用的响应时,我最终得到(Option[String], Option[String])(保存在maybeLoc val中)。在检查maybeLoc的匹配语句中,如果它最终为(None, None),则返回Promise(),因为我需要从flatmap调用中返回Promise

我有两个问题:

1。)在其中一个flatMap或地图调用中处理无法进行任何进一步处理的情况的正确方法是什么?它要求我返回一个承诺,但是当我去兑换它时,空出Promise只会超时,这似乎是一个非常糟糕的主意。

2。)我是否正确地假设对Promise()的调用使得一个空的承诺对象在尝试兑换它时总会超时?我无法从scaladoc那里得知,也无法从谷歌那里找到任何关于它的信息。

我希望我的问题对你有意义并且足够清楚。这是代码:

def bothAsJson(zipcode:String) = Action {
    val promiseOfLoc = Geocode.buildUrlFor(zipcode).get()
    val promiseOfWeather = Weather.buildUrlFor(zipcode, "json").get()

    val result = promiseOfLoc.flatMap { locResp => 
        val maybeLoc = Geocode.extractLocation(locResp.body.toString())
        maybeLoc match {
            case (Some(lat), Some(lng)) => {
                val promiseOfPlaces = Places.buildUrlFor(lat,lng).get()
                promiseOfPlaces.flatMap { placesResp =>
                    promiseOfWeather.map { weatherResp =>
                        (weatherResp.body.toString(), placesResp.body.toString())
                    }
                }
            }
            case _ => Promise()
        }
    }

    Async {
        result.orTimeout("Timeout!", 2000).map {response =>
            response.fold(
                result => Ok("Got:\n\nweather:\n" + result._1 + "\n\nplaces:\n" + result._2),
                timeout => InternalServerError(timeout)
            )
        }
    }
}

1 个答案:

答案 0 :(得分:3)

如果你得到(无,无)你不应该寻找超时,但返回我相信的另一个错误消息。我在下面提供了一个例子。

我认为你需要来自scalaz 7的OptionT。我会把它写成:

import scalaz._
import Scalaz._

def bothAsJson(zipcode:String) = Action {
    val promiseOfLoc = Geocode.buildUrlFor(zipcode).get.map { Option(_.body.toString()) }
    val promiseOfWeather = Weather.buildUrlFor(zipcode, "json").get
       .map{ lockResp => 
           val (lat,lng) = Geocode.extractLocation(locResp.body.toString())
           (lat |@| lng).tupled
       }
    def buildPlaces(lat: String, lng: String) = Places.buildUrlFor(lat,lng).get
       .map { Option(_.body.toString) }

    val result = (for {
       (lat, lng) <- OptionT(promiseOfLoc)
       places     <- OptionT(Places.buildUrlFor(lat,lng).get())
       weather    <- OptionT(promiseOfWeather)
    } yield (places, weather)).run

    Async {
        result.orTimeout("Timeout!", 2000).map {response =>
            response.fold(
                result => {
                  result.map(
                   some => Ok("Got:\n\nweather:\n" + some._1 + "\n\nplaces:\n" + some._2)
                  ).getOrElse(BadRequest("lat/lng failed probably?"))
                },
                timeout => InternalServerError(timeout)
            )
        }
    }
}

使用OptionT,我们可以将flatMap视为Option,如果得到None,则可以不处理任何内容。最后我们留下了Promise[Option[T]],这对此非常好。另一种处理错误的好方法是使用相同的方法使用Either / EtherT。

|@|是申请生成器。它需要2个选项,如果双方都是Option((Int, Int)),则返回Some。如果一方或双方都是None,则会返回None

请注意,这需要一个scalaz Monad[Promise]实例

implicit val PromiseInstance = new Monad[Promise] {
  // override def map[A,B](fa: Promise[A])(f: A => B) = fa.map(f)
  def point[A](a: => A) = Promise.pure(a)
  def bind[A,B](fa: Promise[A])(f: A => Promise[B]) = fa.flatMap(f)
}

另请注意,我已在SO编辑器中编写了所有这些代码,可能会丢失大括号。但是所有的代码都应该或多或少是正确的,我在repl中测试了它的一部分。

请随时向freenode irc上的#scalaz或scalaz google小组寻求帮助。