Callbacks

So, this past week I completed my first major task for Hangify. We are currently working on version 1.0 of the iOS mobile app. The app’s front end is written in Angular, and then we use Cordova platform and the Ionic framework to compile to an iOS app. One of my jobs in this sprint was to refactor storage of login credentials from local browser storage to native application storage on the iDevice.

At our sprint planning meetings, we begin by going through our tasks in Asana from the previous sprint, which hopefully have all been finished. After that, we assign new tasks to everyone for the next sprint, and assign each task a number from the Fibonacci series to quantify how difficult/how long the will be. The difficulty for this process for a newbie like me is, it can be hard to make a good guess at how difficult a task will be when you don’t have much prior experience. So, we ended up assigning this task a difficult of 2. Little did I know…

So after finishing the two other tasks I were assigned, which were not too difficult (enabling infinite scrolling in our main ‘experience’ feed, and fixing a bug with our search feature), I began to work on my storage task, and immediately ran into my first roadblock… I don’t own a Mac, or even an iDevice to test this feature. I eventually got through this roadblock by installing a Hackintosh VM on my Linux machine (I plan on blogging about this task another time).

Once my Mac VM was up and running, I figured it was a simple matter of learning the new API, and rewriting the API calls of browser storage to native storage. How wrong I was, because I was about to learn about the craziness of asynchronous callbacks.

See, I learned that JavaScript has no concept of threads. Everything runs on a single thread, meaning that if a function is going to take a lot of time, it will hold up all of the code after it. This is referred to as blocking. This obviously is undesirable. Imagine a function who’s job it was to retrieve data from a server. If the server connection was slow, this will cause of the rest of the code after this function to stop executing until the operation is completed.

To solve this problem, functions like this are written to run asynchronously. In an asynchronous function, two callback functions are passed to this function, one to execute if and when it completes successfully, and one for when it fails, so imagine a situation like this:

var data = getDataFromServer(arguments);
data.doSomethingWithIt();

This will fail to work, because the second line of code will execute before any data is returned from the getDataFromServer function. The variable data will be undefined at run time, and therefore will not have the function doSomethingWithIt.

In order for this to work, you need callback functions:

getDataFromServer(arguments, successCallback, failCallback);

function successCallback(resp) {
  resp.doSomethingWithIt();
}

function failCallback(err) {
  console.log('getting data failed', err);
}

Looking back, this makes a lot of sense, but I had a hard time wrapping my head around this concept at first.

Now, the reason why this was an issue for me was because local browser storage can run synchronously. There is no delay when assigning a variable to browser storage, because it is essentially just part of the DOM. However, native application storage is run asynchronously, as there could be a delay in saving data to the device. So, this would not be a matter of having to refactor from one API to another, the whole architecture of the code, from the API service on up would have to be refactored.

To make this even more complicated there were several layers of asynchronous callbacks that had to be worked juggled at the same time. Let’s imagine the task of logging into the app:

  • User enters login information, which is sent to the server.
  • If authorized, the server returns an object with auth information (user id, auth token, etc.) (asynchronous).
  • This auth information is stored in native storage (asynchronous).
  • The user is redirected to the main feed of the app.
  • An API method is called to get the content of the main feed. However, before this happens, the auth token is retrieved from native storage to be sent with the request (asynchronous).
  • The content is returned from the server (asynchronous).
  • The content is displayed in the feed.

As you can see, there are four asynchronous functions in that one user interaction. It became mind-boggling at times to balance all of the callbacks and follow the flow of the code.

Another issue with this was that it was now impossible to debug any of this in Chrome dev tools, as every API call relies on accessing native storage in order to pass the auth token. Debugging in xCode is a pain, as the project code has to be rebuilt on every change. At the time, I was running my Hackintosh VM on an older computer, which made builds painfully slow. To solve this problem, I wrote storage fallback methods that rely on browser storage when running in the browser. To make these methods asynchronous (and therefore compatible with the native storage code), accessing browser storage is wrapped in setTimeout functions with a delay of 1 millisecond.

This seemingly simple task ended up being very complicated. The API service had to be completely refactored (which gave me the opportunity to add in JSDoc documentation in for all of the methods). This changed the signature of every method, so every API call in the rest of the codebase had to be refactored as well. I did all of this in the browser, relying my fallback storage method.

Eventually, I began testing in xCode, and after fixing a few bugs, got everything working. I am happy to say that this feature will make it into the next update.

While this experience was difficult, if not painful at times, I have learned a great deal from it. In the process, I:

  • Basically rewired my brain on how I think about the execution of my JavaScript code.
  • Became an expert on asynchronous JavaScript functions.
  • Touched the entire codebase, and gave myself a firm understanding of how everything works.
  • Got very comfortable with the Angular framework.
  • Learned how to write JSDoc documentation.
  • Successfully set up a Virtual Machine with OS X on it.
  • Learned the basics of xCode.
  • Gained a basic understanding of Cordova and Ionic.
  • Learned to not give up. If you think about the problem long enough, you’ll figure it out. Sometimes you just have to close the laptop and give it 24 hours.
  • Learned to ask for help when needed. There were many late night Slack sessions with my mentor/development team leader about how to do all of this, including one in-person pair-programming session. At times I felt like I total newb for asking for help. However, in most cases, he was almost as confused as I was, and it was great to talk through these problems and solve them together.I feel a great sense of accomplishment having completed this task. I have significantly contributed to the app, and had a great experience. Tomorrow, we have our next sprint planning meeting. I look forward to my new tasks at hand, and what I might learn from them.

Leave a Reply

Your email address will not be published. Required fields are marked *