📘

【Scala】Future.sequenceで実行した場合に元のSeqの順序が保持されるのを確認してみる

2021/11/25に公開

概要

Scalaで非同期処理を実装した場合Futureを使用されると思います。Futureの機能として、複数のFutureを並列に実行するものがありまして、Futureをマスターする -コンパニオンオブジェクト操作-で紹介されているFuture.sequenceが挙げられます。
このFuture.sequenceですが、実行された結果は元のSeqが保持される仕様となっています。Asynchronous programming with Scala Futuresの記事で、紹介されている内容になります。ということで、この順序が保持されるのを実際に実装して確認してみたので、メモ書きとして残しておきます。

確認方法

同エンドポイントのAPIにリクエストのソート順(Seqで持つ)だけ変えて、Future.sequenceでリクエストを投げます。結果のSeqから、リクエストのソート順のSeqと同じ順序になっているか確認していきます。
なお、 確認用のAPIに今回はgBizINFOを使用しました。

実装サンプル

以下のソースで結果も順序が保持されることを確認しました。全ソースコードはこちらにアップしています。
なお、フレームワークにはAkkaHttp、JSONのパースにはcirceを使用しています。

CompanySearchUseCase.scala

import akka.actor.ActorSystem
import companyInfo.model.api.InitialResponse
import companyInfo.model.rest.GbizResponse
import companyInfo.rest.CompanyRestClient
import companyInfo.rest.query.CompanyQueryBuilder
import io.circe.generic.auto._
import io.circe.parser._

import scala.concurrent.duration.Duration
import scala.concurrent.{Await, Future}
import scala.util.{Failure, Success}

object CompanySearchUseCase {

  implicit val system = ActorSystem()
  implicit val executionContext = system.dispatcher

  def getInitialDisplayCompanyInfo(): Either[Throwable, InitialResponse] = {
    // このソートキーでリクエストを投げる
    val sortKeys = Seq[String]("npmr", "roa", "roe", "equityRatio")

    val futureSeq = Future.sequence(sortKeys.map(k =>
    // APIリクエストを投げる処理(内容は記載を割愛)
    CompanyRestClient.sendRequestToGbiz(
      CompanyQueryBuilder.getQuery(sort = k)
    )))

    // 結果確認用のclass
    val initialRes = InitialResponse()
    var exceptionSeq = Seq.empty[Throwable]

    val result = Await.ready(futureSeq, Duration.Inf).value.get
    // sortKeysのSeqの順序になってるか確認する
    result.foreach(seq => seq.zipWithIndex.foreach(res => {
      res._1 match {
        case Success(r) => {
          decode[GbizResponse](r) match {
            case Right(bizRes) => {
              val companyResponses = bizRes.results.bindings.map(_.convertCompanyResponse())
	      // sortKeysの最初はnpmr
              if (res._2 == 0) {
                initialRes.byNpmrSortCompanies = companyResponses
	      // sortKeysの2番目はroa
              } else if (res._2 == 1) {
                initialRes.byRoaCompanies = companyResponses
	      // sortKeysの3番目はroe
              } else if (res._2 == 2) {
                initialRes.byRoeCompanies = companyResponses
	      // sortKeysの最後はequityRatio
              } else {
                initialRes.byEquityRatioCompanies = companyResponses
              }
            }
            case Left(ex) => exceptionSeq :+= ex
          }
        }
        case Failure(ex) => exceptionSeq :+= ex
      }
    }))

    if (exceptionSeq.isEmpty) {
      Right(initialRes)
    } else {
      Left(exceptionSeq.head)
    }
  }
}

Discussion