Home > OS >  Returning values into GlobalScope launch using Spring
Returning values into GlobalScope launch using Spring

Time:11-09

I have an endpoint exposed, that is launching a coroutine:

val apiCall = ApiCall()

@GetMapping("/example")
fun example(@RequestParam paramExample:String):Int{
    GlobalScope.launch{
        return apiCall.callApi(paramExample)
    }
}

This function is calling another external API, using Retrofit:

suspend fun callApi(param:String):Int{
    var tot_records =0
    val retrofit: Retrofit = Retrofit.Builder()
            .baseUrl(appProperties.sampleUrl)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    val service = retrofit.create<ResponseService>(ResponseService::class.java)

    service.getResponse().enqueue(object : Callback<Response> {
        override fun onFailure(call: Call<Response>, throwable: Throwable) {
            println("Error")
            println(throwable.stackTrace)
        }

        override fun onResponse(call: Call<Response>, response: Response<Response>) {
            println("OK")
            println(response.body())
            println("Tot records")
            tot_records = response.body()?.tot_records!!
        }
    })
    return tot_records
}

The problem is that I can't launch this, the error is: 'return' is not allowed here

Any idea how to fix it and whats is happening?

Thanks for your help

CodePudding user response:

It seems like you can't decide if you want your code to be synchronous (so code waits for its subtasks to finish before continuing) or asynchronous (it launches operations in the background). You intend to return a result from example(), so you need it to be synchronous, but you immediately use launch() to invoke callApi() asynchronously. The same in callApi() - you intend to return from it (so synchronous), but you invoke Retrofit using callbacks (so asynchronous). Note that callApi() has exactly the same problem as example(). Even if it compiles, it still does not really work properly. It always returns 0, because tot_records is returned before being set.

You have to decide between asynchronous and synchronous and stick to it. If you want to go fully asynchronous, then you need to redesign both callApi() and example() to return their results either with callbacks or futures.

However, I suggest going fully synchronous, utilizing Kotlin suspend functions. Make all functions suspend: example(), callApi() (it is already) and ResponseService.getResponse(). The last one will look something like:

suspend fun getResponse(): Response

Then remove GlobalScope.launch(), and almost everything inside enqueue(). Instead, service.getResponse() will return Response object directly, so you can just return its tot_records property.

Also note that in your original code you ignored failures. After above change service.getResponse() will throw exceptions on failures, so you have to handle them.

CodePudding user response:

This solution seems that works:

This is the endpoint declaration:

@GetMapping("/example")
suspend fun example(@RequestParam param:String):CustomResponse{
    return coroutineScope {
        val job = async{apiCall.callApi(param)}
        job.await()
    }
}

And this is my function that is calling an external API:

suspend fun callApi(param:String):CustomResponse{
    var responseCustom = CustomResponse()
    val retrofit: Retrofit = Retrofit.Builder()
            .baseUrl(appProperties.reservationUrl)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    val service = retrofit.create<CustomResponseService>(CustomResponseService::class.java)

    responseCustom = service.getResponse(appProperties.token, param).execute().body()!!

    return responseCustom
}
  • Related