为什么调用 API 或启动协程的函数返回空值或空值?

(免责声明:当人们通过Facebook,firebase等请求使用异步操作时询问数据是否为空/不正确时,会产生大量问题。我对这个问题的意图是为从Android中的异步操作开始的每个人提供一个简单的答案)

我正在尝试从我的一个操作中获取数据,当我使用断点或日志调试它时,值就在那里,但是当我运行它时,它们总是空的,我该如何解决这个问题?

火力基地

firebaseFirestore.collection("some collection").get()
    .addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
        @Override
        public void onSuccess(QuerySnapshot documentSnapshots) {
            //I want to return these values I receive here... 
        });
//...and use the returned value here.

脸书

GraphRequest request = GraphRequest.newGraphPathRequest(
    accessToken,
    "some path",
    new GraphRequest.Callback() {
        @Override
        public void onCompleted(GraphResponse response) {
            //I want to return these values I receive here...
        }
    });
request.executeAsync();
//...and use the returned value here.

Kotlin 协程

var result: SomeResultType? = null
someScope.launch {
    result = someSuspendFunctionToRetrieveSomething()
    //I want to return the value I received here... 
}
Log.d("result", result.toString()) //...but it is still null here.

等。


答案 1

什么是同步/异步操作?

好吧,同步等待任务完成。在这种情况下,您的代码将“自上而下”地执行。

异步在后台完成任务,并在任务完成时通知您。

如果要通过方法/函数从异步操作返回值,则可以在方法/函数中定义自己的回调,以便在这些值从这些操作返回时使用这些值。

以下是Java的方法

首先定义一个接口:

interface Callback {
    void myResponseCallback(YourReturnType result);//whatever your return type is: string, integer, etc.
}

接下来,将方法签名更改为如下所示:

public void foo(final Callback callback) { // make your method, which was previously returning something, return void, and add in the new callback interface.

接下来,无论您以前想在哪里使用这些值,请添加以下行:

callback.myResponseCallback(yourResponseObject);

例如:

@Override
public void onSuccess(QuerySnapshot documentSnapshots) {
    // create your object you want to return here
    String bar = document.get("something").toString();
    callback.myResponseCallback(bar);
})

现在,您之前调用的方法名为:foo

foo(new Callback() {
        @Override
        public void myResponseCallback(YourReturnType result) {
            //here, this result parameter that comes through is your api call result to use, so use this result right here to do any operation you previously wanted to do. 
        }
    });
}

你如何为Kotlin做到这一点?(作为一个基本的例子,你只关心一个结果)

首先将方法签名更改为如下所示的内容:

fun foo(callback:(YourReturnType) -> Unit) {
.....

然后,在异步操作的结果中:

firestore.collection("something")
         .document("document").get()
         .addOnSuccessListener { 
             val bar = it.get("something").toString()
             callback(bar)
         }

然后,您之前会将方法称为 ,现在执行以下操作:foo

foo() { result->
    // here, this result parameter that comes through is 
    // whatever you passed to the callback in the code aboce, 
    // so use this result right here to do any operation 
    // you previously wanted to do. 
}
// Be aware that code outside the callback here will run
// BEFORE the code above, and cannot rely on any data that may
// be set inside the callback.

如果你的方法以前采用了参数:foo

fun foo(value:SomeType, callback:(YourType) -> Unit)

您只需将其更改为:

foo(yourValueHere) { result ->
    // here, this result parameter that comes through is 
    // whatever you passed to the callback in the code aboce, 
    // so use this result right here to do any operation 
    // you previously wanted to do. 
}

这些解决方案演示如何创建方法/函数,以从通过使用回调执行的异步操作返回值。


但是,重要的是要了解,如果您不有兴趣为这些创建方法/函数:

@Override
public void onSuccess(SomeApiObjectType someApiResult) {
    // here, this `onSuccess` callback provided by the api 
    // already has the data you're looking for (in this example, 
    // that data would be `someApiResult`).
    // you can simply add all your relevant code which would 
    // be using this result inside this block here, this will 
    // include any manipulation of data, populating adapters, etc. 
    // this is the only place where you will have access to the
    // data returned by the api call, assuming your api follows
    // this pattern
})

答案 2

我反复看到这种性质的特殊模式,我认为对正在发生的事情的解释会有所帮助。该模式是一个函数/方法,它调用 API,将结果分配给回调中的变量,并返回该变量。

以下函数/方法始终返回 null,即使来自 API 的结果不为 null。

科特林

fun foo(): String? {
   var myReturnValue: String? = null
   someApi.addOnSuccessListener { result ->
       myReturnValue = result.value
   }.execute()
   return myReturnValue
}

Kotlin 协程

fun foo(): String? {
   var myReturnValue: String? = null
   lifecycleScope.launch { 
       myReturnValue = someApiSuspendFunction()
   }
   return myReturnValue
}

爪哇 8

private String fooValue = null;

private String foo() {
    someApi.addOnSuccessListener(result -> fooValue = result.getValue())
        .execute();
    return fooValue;
}

Java 7

private String fooValue = null;

private String foo() {
    someApi.addOnSuccessListener(new OnSuccessListener<String>() {
        public void onSuccess(Result<String> result) {
            fooValue = result.getValue();
        }
    }).execute();
    return fooValue;
}

原因是,当您将回调或侦听器传递给 API 函数时,该回调代码只会在 API 完成工作后的某个时候运行。通过将回调传递给 API 函数,您可以对工作进行排队,但当前函数(在本例中)在工作开始之前和运行回调代码之前立即返回。foo()

或者在上面的协程示例中,启动的协程不太可能在启动它的函数之前完成。

调用 API 的函数无法返回回调中返回的结果(除非它是 Kotlin 协程挂起函数)。解决方案(在另一个答案中解释)是使自己的函数采用回调参数而不返回任何内容。

或者,如果您正在使用协程,则可以使函数挂起,而不是启动单独的协程。当您具有挂起函数时,必须在代码中的某个位置启动协程并处理协程的结果。通常,您将在生命周期函数(如 )或 UI 回调(如 OnClickListener)中启动协程。onCreate()


推荐