How to obtain the return value after executing an asynchronous method with runJavaScript ?
Problem Description
The official documentation mentions that the runJavaScript method cannot obtain the return value when executing asynchronous methods. How can this issue be resolved?
Background Knowledge
In applications, the use of JavaScript in front-end pages is primarily divided into: the application side calling front-end page functions, and the front-end page calling application-side function methods to invoke JavaScript-related functions on the front-end page.
- Application-side invocation of front-end page functions: The application side can call JavaScript-related functions on the front-end page through the runJavaScript() and runJavaScriptExt() methods.
- Front-end page calls application-side functions: There are two ways to register application-side code. One is to call it during the initialization of the Web component using the
javaScriptProxy()interface. The other is to call it after the Web component has been initialized, using theregisterJavaScriptProxy()interface. Both methods need to be used in conjunction with thedeleteJavaScriptRegisterinterface to prevent memory leaks.
Troubleshooting Process
Problem Analysis
The challenge is to execute an asynchronous JavaScript method in a webview and retrieve its return value usingrunJavaScript(), which itself is asynchronous.-
Key Points Verification
-
Asynchronous Limitation:
runJavaScript()is asynchronous, so directly capturing its return value is not possible. - Workaround Approach:
- Define a synchronous wrapper function in the frontend to call the asynchronous method.
- Use a callback mechanism to pass the result back to the app side.
-
Asynchronous Limitation:
-
Potential Issues
-
Memory Leaks: Improper use of
deleteJavaScriptRegistercould lead to memory leaks. - Lifecycle Management: Ensure the JavaScriptProxy is properly registered and unregistered during app lifecycle events.
-
Memory Leaks: Improper use of
Solution
The reason why runJavaScript cannot obtain the return value when executing asynchronous methods is as follows:
runJavaScript itself is executed asynchronously, which means that runJavaScript is essentially an asynchronous method. Currently, there is no solution to obtain the return value when calling another asynchronous method.
We can only approach this issue from other angles.
- Define a synchronous method in the front-end code and execute it using runJavaScript.
- Within the front-end synchronous method, call the actual front-end asynchronous method that you intend to execute.
- After obtaining the return value from the front-end asynchronous method, the front-end invokes an application-side method to pass the return value as a parameter.
This resolves the issue of not being able to obtain return values when executing asynchronous methods with runJavaScript.
- indexByTest.html.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>title</title>
</head>
<body>
<p>Front-end page</p>
</body>
<script>
const testAsync = (a, b, c) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(`Asynchronous return${a},${b},${c}`)
}, 3000)
})
}
function testSync(a, b, c) {
testAsync(a, b, c).then(res => {
testObjName.testSyncCallback(res)
})
}
</script>
</html>
- Index.ets.
import { webview } from '@kit.ArkWeb';
class TestObj {
obj?: Index
constructor(obj: Index) {
this.obj = obj
}
testSyncCallback(res: string) {
console.info('testSyncCallback: ', res)
if(this.obj) {
this.obj.msg = res
}
}
}
@Entry
@Component
struct Index {
private webviewController: WebviewController = new webview.WebviewController()
private localPath = $rawfile('indexByTest.html')
private testObj = new TestObj(this)
@State msg: string = ""
aboutToAppear(): void {
// You need to apply for the ohos.permission.INTERNET permission.
webview.WebviewController.setWebDebuggingAccess(true);
}
aboutToDisappear(): void {
this.webviewController.deleteJavaScriptRegister('testObjName')
}
build() {
Column({space: 6}) {
Web({controller: this.webviewController, src: this.localPath})
.javaScriptAccess(true)
.javaScriptProxy({object: this.testObj, name: 'testObjName', methodList: ['testSyncCallback'],
controller: this.webviewController})
.width('60%')
.height('60%')
Text(this.msg ? "The data returned by JS: " + this.msg : "")
.fontSize(12)
Button('Return value of an async func')
.fontSize(8)
.height(30)
.width('85%')
.onClick(() => {
this.webviewController.runJavaScript('testSync(1, 2, "666")');
})
}
.width('100%')
.height('100%')
.padding(20)
}
}


Top comments (0)