— React Native, Node, Javascript — 3 min read
First let me preface this by stating I'm not the biggest fan of Javascript. Don't get me wrong, I don't hate it, I'm just used to doing things in a ways that don't translate well to JS. This is probably a case of
You can't teach an old dog new tricks
but I'm definitely doing my best to learn the new trick! It's just taking some time.
A year or so ago I wrote my first React Native Module react-native-bluetooth-classic and I'm currently in the process of adding some new functionality (multiple devices, etc.) and updating the example application with some much needed love. While doing so, I migrated the connection logic from the main App
to the ConnectionScreen
:
1// Ya, I also like working with classes instead of functions, I'm terrible2export default ConnectionScreen extends React.Component {3 constructor(props) {...}4
5 async componentDidMount() {6 try {7 let connection = await RNBluetoothClassic.connect(this.props.device.address, {});8 this.setState({connection});9 } catch(error) {10 addMessage(`Unable to connect to ${this.props.device.address}`);11 }12 }13}
Which I thought was exactly what should happen... The issue here is that when mounting the ConnectionScreen
there was a lag (the time it took to connect/error) while attempting the connection. Very noticeable and very uncool!
Since I was following all the documentation and all the tutorials it didn't much make sense what was going on:
componentDidMount
set to async
so that it would return a Promise
let connection = await RNBluetoothClassic.connect(this.props.device.address, {});
and my native method was configured with a Promise
as it's last paraemeter.But there was still that noticable blocking in what should be a non-blocking call. After looking around there was one post on Stack Overflow (a hero) https://stackoverflow.com/questions/58506993/why-does-async-work-in-componentdidmount-lead-to-visual-lag-when-navigating-unle#comment113857184_58506993 that brought up the idea of task queue and job queue, which after doing some research should have been microtask and macrotask queues.
Following the rabbit hole that is NodeJS documentation and tutorials, I found another amazingly informative posts:
After reviewing all the information provided there were a few assumptions that were made (probably wrong, but seemed valid):
ConnectionScreen
ConnectionScreen.componentDidMount
Promise was created and added to the queueRNBluetoothClassic.connect
Promise was added to the queue - because it came from the same macrotask it must run before any others can. This meant that event though the native connection was occurring in another thread (Java/Swift) React Native couldn't do anything about it due to the promise placement.Maybe I'm jaded or maybe I don't fully get it, but it just bothers me.
With all that said, the point was to get my ConnectionScreen.componentDidMount()
not blocking on the RNBluetoothClassic.connect()
call. Now that I've got a (somewhat) better understanding of how things go together, the answer within the Stake Overflow question makes sense - force the connection call to be pushed to a new macrotask queue so that the current loading isn't blocked, the screen can show, then the connection can happen downstream:
1export default class ConnectionScreen extends React.Component {2 constructor(props) {...}3
4 async componentDidMount() {5 setTimeout(this.connect, 0); // jump to next macrotask6 }7
8 async connect() {9 addMessage(`Attempting connection to ${this.props.device.address}`);10
11 try {12 let connection = RNBluetoothClassic.connect(this.props.device.address, {});13 this.setState({connection});14 } catch(error) {15 addMessage(`Unable to connect to ${this.props.device.address}`);16 }17 }18}
and voila! We have a non blocking screen with a successful connection happening.
Now some questions that I have with this, based on how other internal libraries are designed (and for that I probably need to dig through source) is whether all my React Native (JS) module functions wrap the Native implementations in a setTimeout()
so that they all do this automatically? Or should it be left up to the implementation to determine? Is it the Javascript or Native implementations of FS and FETCH that perform this extra step of adding to a separate polling?
Thoughts for later.
Please, if you come across this and you feel like:
please let me know! I'm definitely well behind the eight ball with Javascript but I'm trying to learn to love it!