facebook / react-native

A framework for building native applications using React
https://reactnative.dev
MIT License
118.75k stars 24.29k forks source link

How to run heavy JS blocking tasks which won't affect the main UI thread? #19456

Closed radi-cho closed 6 years ago

radi-cho commented 6 years ago

Hello! I'm working on chess app which makes a lot of simulations for analytics and AI play mode. This freezes the UI and main thread for 2-3 seconds because of all processes running. I had BIG research on how to solve that.

Firstly I want to mention that async functions and Promises do not help because they're running in the main thread and it still freezes. And also libraries like hamsters.js cannot help.

Also I found some RN libs and plugins for workers: https://github.com/fabriciovergal/react-native-workers, https://github.com/devfd/react-native-workers, which are no longer maintained and don't work with newer versions. There is one more library: https://github.com/joltup/react-native-threads. It also fails:

Also there's requestAnimation API wich cannot help in my case, because the whole processing is provided by library called RSG Chess API so I run:

AI(something)

instead of:

for (something) {
  something
}

so I cannot break the computing into smaller processes.

Another possible solution is to use "invisible" WebView in the app and there run the processes and comunicating via the window.postMessage API. Link to blog post: https://medium.com/@inkdrop/a-simple-way-to-run-js-in-background-thread-on-react-native-8fff345576da. But that is very tricky and hacky.

I had big research about the problem

Stack Overflow

Discussion Forum - https://discuss.reactjs.org/t/which-is-the-best-way-to-run-heavy-tasks-in-react-native/11932

Another sources:

Reactiflux Chat - didn't get any help.

Please help!

react-native-bot commented 6 years ago

It looks like your issue may be incomplete. Are all the fields required by the Issue Template filled out?

If you believe your issue contains all the relevant information, let us know in order to have a maintainer remove the No Template label.

react-native-bot commented 6 years ago

If you are still encountering the issue described here, please open a new issue and make sure to fill out the Issue Template when doing so.

vikas5914 commented 6 years ago

@radi-cho Can you tell me how did you fix the problem.

radi-cho commented 6 years ago

@vikas5914 Yup... As I described before there are already a lot of posts and articles about this topic. However they're incomplete and little bit tricky.

Every step is listed below. Before to read them please join my slackroom http://rsg-slack.herokuapp.com and I'll help you achieve multithreading. Also this solution is implemented in the RSG-Chess-mobile repository. Take a look here:

  1. Read this medium post: https://medium.com/@inkdrop/a-simple-way-to-run-js-in-background-thread-on-react-native-8fff345576da
  2. You'll need to make html file or provide it as string. Example:
    // in your js code
    const html = `
    <html>
    <body>
    <script>
      // The code which will be run on the web thread.
    </script>
    </body>
    </html>`;

    If you want to use any node/ES6 modules or npm packages you'll need to make a new file and then compile it with webpack and babel or use online tool. In my case I needed the RSG Chess API module and copy-pasted its code from the official repo, like that: image

  3. Add WebView to your app.
    // example from the Medium post, but using your custom html and scripts
    <WebView
     ref={el => this.webView = el}
     source={{html: html}}
     onMessage={this.handleMessage}
    />

the html const must be a string. You can also create html file and use the nodejs apis to pass it to the WebView.

  1. Initialize global functions to pass arguments/data to it.
    <script>
    window.AI = function (params) {
      // code here
      // return some response to the main RN JS thread
      window.postMessage();
    }
    </script>
  2. Call the global functions from the main thread. To run the methods from the main RN JS thread you can simple use the injectJavaScript method. In my case I used:
    this.webView.injectJavaScript(
    `AI(${params})`
    )

this.webView is defined by ref={el => this.webView = el}

In the example above params is stringified object, because you must send and recieve only STRINGS via the postMessageAPI and the injectJavaScript method. Don't worry you can pass objects by using JSON.stringify and JSON.parse

  1. Send back the results When your tasks in the web thread are ready you can send back response to the main thread
    window.postMessage(/* string, stringified object/array or stringified function */);

    To recieve the data you can use handleMessage method predefined in the onMessage prop of the WebView.

    <WebView onMessage={this.handleMessage} />
    handleMessage = msg => {
    msg.nativeEvent.data
    }

Hopefully this will help!

invyctus92 commented 6 years ago

thanks to @radi-cho and his project RSG Chess project, I solved the problem. The example is: https://pastebin.com/DG490YJw https://pastebin.com/t6w1i9EX I have react native 0.53.0.

radi-cho commented 6 years ago

So we with @invyctus92 resolved his issue. There were two problems with his code.

  1. You need to pass source={{ html: html }} to the webView Wrong case: source={{ html }} which will pass { html: true } to the WebView Right case: source={{ html: html }} which will pass { html: CONTENT_IN_YOUR_html_VAR }

  2. He needed to run the web thread in the componentDidMount method, but sometimes the webView is not yet loaded at this time. For that purpose you can use the state or a callback to check if everything is loaded.

Working examples:

Also I tried to make this issue as clean as possible, so If someone have additional questions please contact me here http://rsg-slack.herokuapp.com But if someone have better solution or code which will help the community please post here!