Scala 未来奇怪的错误完成

Scala future strange error on completion

我刚刚开始使用期货,偶然发现了一个对我来说很奇怪的错误:

使用play-ws执行post请求并映射结果:

wsClient.url(url).withHeaders("Content-Type" -> "application/json")
      .post(payload)
      .map { wsResponse =>
        if (!(200 to 299).contains(wsResponse.status)) {
          sys.error(s"Received unexpected status, open-cpu error ${wsResponse.status} : ${wsResponse.body}")
        }
        println(s"OK, received ${wsResponse.body}")

        wsResponse.json.validate[Seq[MyClass]] match {
          case JsSuccess(result, _) => result.map(outlierRes => Map("key" -> outlierRes.attr, "key2" -> outlierRes.attr2, "key3" -> outlierRes.val3))
          case JsError(error) => throw new MyException(error.toString())
        }
      }

工作正常。 body的println显示都在,验证成功

问题出在这里:aggregatedData = Await.result(theFutureFromAbove, 20.minutes)

当通过交互式控制台 运行 时,此语句因以下内容而崩溃:

MyException
        at $anonfun.apply(<console>:44)
        at $anonfun.apply(<console>:44)
        at scala.util.Success$$anonfun$map.apply(Try.scala:206)
        at scala.util.Try$.apply(Try.scala:161)
        at scala.util.Success.map(Try.scala:206)
        at scala.concurrent.Future$$anonfun$map.apply(Future.scala:235)
        at scala.concurrent.Future$$anonfun$map.apply(Future.scala:235)
        at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:32)
        at scala.concurrent.impl.ExecutionContextImpl$$anon.exec(ExecutionContextImpl.scala:107)
        at scala.concurrent.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
        at scala.concurrent.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)
        at scala.concurrent.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
        at scala.concurrent.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)

这会提示解析异常。

然而,当 运行 通过 sbt run 时,有一个不同的例外:

java.lang.NullPointerException

类似于这一行:wsClient.url(baseUrl + url).withHeaders("Content-Type" -> "application/json")

编辑

它似乎被触发:wsClient.close() 好像我在 future 完成之前关闭了 wsClient。 但是,在文档中它指出

If you create a WSClient manually then you must call client.close() to clean it up when you’ve finished with it.

那么我应该在哪里关闭它呢?最初,我认为在 Await.result 之后关闭是安全的,但仍然会引发错误。

编辑 2

wsResponse.json.validate[Seq[MyClass]].fold(
          error => {
            println(error)
            Future.failed(new MyException("parsing failed" + error))
          },
          result => result.map(data => Map("period" -> data.period, "amount" -> data.totalAmount, "outlier" -> data.isOutlier))
        )

但这仍然无法编译,因为 Future[nothing] 与我的 return 类型不匹配 Future[Seq[Map[String, Any]]]

基于https://github.com/studiodev/Mocky/blob/master/app/services/GithubRepository.scala终于找到了类似

的东西
private def parseGistResponse(response: WSResponse): Future[GistResponse] = {
    if (response.status < 400) {
      logger.debug(s"<< (${response.status}) ${response.body}")
      response.json.validate[GistResponse].fold(
        error => {
          logger.error(s"Unable to parse GistResponse: $error")
          Future.failed(new RuntimeException("parse-json-failed"))
        },
        gistResponse => Future.successful(gistResponse))
    } else {
      logger.error("Unable to parse GitResponse, cannot contact WS\n" + debugResponse(response))
      Future.failed(new RuntimeException("ws-error"))
    }
  }