What difference between methods callAsyncJavaScript
and evaluateJavaScript
?
Using it with same script like:
evaluateJavaScript("document.documentElement.outerHTML.toString()")
and
callAsyncJavaScript("document.documentElement.outerHTML.toString()")
returns different results: HTML in first, and .sucess(nil) in second...
CodePudding user response:
There are 3 methods that allow to run JS from WKWebView (well, there's more signatures, but 3 principle methods):
The oldest and most primitive is
evaluateJavaScript(_:completionHandler:)
, which allows to run arbitrary piece of code. This day and age, there's no excuse to use, as it has no benefits over the other methods.The more modern improved version is
evaluateJavaScript(_:in:in:completionHandler:)
which was introduced in iOS 14. It can be used to run synchronous code. It uses a modern way to present a result -Result<Any, Error>
, which allows to encapsulate any sort of result, success or failure in a single convenient to use object. But the key difference is that it allows you to specify the context of the script, which prevents conflicts between different scripts:
Executing a script in its own content world effectively gives it a separate copy of the environment variables to modify.
- But neither of these functions is able to support asynchronous JavaScript code, i.e. promise. You can implement promise execution if you combine
evaluateJavaScript
withWKScriptMessage
(e.g. as explained here), but it's very awkward. SocallAsyncJavaScript(_:arguments:in:in:completionHandler:)
solves that problem: you can use it to execute a Promise, without additional help ofWKScriptMessage
, which makes code less distributed, and easier. Here's an example of how it's done.
It also has 2 additional advantages of this method:
- The first argument is no longer a function name, but
functionBody
, which becomes an anonymous function, and prevents the naming conflicts possible due to global scope of the function. - And also arguments are separated from the body as
[String : Any]
, which allow us to define function body somewhere statically, and reuse it by passing the arguments (as opposed to injecting them to function body as you need withevaluateJavaScript
.
So you should be using this method for executing asynchronous JS code, and you can use it for the synchronous code.
One more thing: with callAsyncJavaScript
you need to remember that you are defining the function body, so if you want something returned, you need to use the word return
(as opposed to evaluateJavaScript
, where you defined a piece of code to run, which can have no return
, unless you define a function).