In this open-ended series I'll be showcasing a snippet of buggy jQuery code that you might encounter, explain what the problem is, and then identify how you can easily resolve the issue.
As this series progresses my example code may not use all the best practices that I would normally use in my everyday development. This is partly due to me wanting to show code you might encounter in the wild, but also because I want these code snippets to be easily understood so that the main concept can be revealed. In order to do that a healthy balance will need to be maintained, which is tricky ;)
You have a list of individuals in a table. Each row has an alternating background color (zebra). You can delete them by clicking an icon to the right of each row.
When the trash icon is clicked, a request goes to the server to delete that individual by it's ID. If the action was successful then the row should fade out and then be removed from the DOM. After all is said and done, the rows should be zebra'ed again.
The following code was inspired by an issue a friend of mine, Casey Picker from LamplightMedia.net, had last week and I helped him isolate the problem. If you don't spot the error right away I have a simplified version of the same underlying issue at the end of the post.
When you execute the above code you'll notice that when you delete one of the rows the alternating background colors get all out of sync.
Since the row that is being deleted is happening in the success callback function It seemed logical to put the zebraTable call in the complete callback function. That seems right, right? Based on the jQuery documentation the complete callback is only fired after the success function. But why am I having this problem?
Well, the problem is the classic case of treating asynchronous code as synchronous. You might think to yourself, "I know AJAX is asynchronous, but the problem is happening after we've received a response, right?" The answer to that is "Yes", but you the act of animating the fading of the row is also asynchronous.
Once the program starts the hide animation the control of execution moves on to trigger the complete callback function (where the zebraTable call is taking place). After 500 milliseconds, when the row has completed fading out, control is given back to the hide callback which finally removes the row. The bug is that the code is zebra-fying the table before the row is deleted, which isn't what you intended.
The solution to fix this problem is really simple and straightforward. All you really need to do is to move the zebraTable function call out of the complete callback and immediately after you remove the row from the hide callback.
If you test out the code again below you'll notice that now we have the desired behavior that we were wanting all along. If you delete a row the rows will re-zebra-fy themselves as expected.
The above example was slightly complex, but the underlying problem of treating asynchronous code as synchronous is common. Here is another example, but this time dramatically simplified.
The above code snippet ( jsFiddle ) declares a contact variable and then makes an AJAX call to retrieve contact information from the server. On the next statement the code is assuming that the contact is already available to display in the console. Unfortunately, since the AJAX is asynchronous the result will not be what you expected.
The most simple fix to the above code snippet is to move the console.log statement to after the AJAX call has successfully returned the server with your contact data as show in the below code snippet ( jsFiddle ).
Technically this fixes your error, but you might additionally refactor this code to make a callback, trigger an event, or publish a message that the data was retrieved. This would help separate the data access code from your user interface code.
The key concept to remember here is to not treat asynchronous code as synchronous. Most of us are aware that AJAX calls are asynchronous, but we also need to remember that animations are asynchronous as well. In addition setTimeout and setInterval are also asynchronous.
Until next time...