Async Forms: Avoiding jQuery callback hell

Ismayil Khayredinov
ITNEXT
Published in
3 min readAug 1, 2019

--

Image Source

Alas, jQuery isn’t dead yet. Once beloved state-of-the-art library, now feels like a massive thorn in a developer’s ass. Regardless, there are thousands of projects that still use it, and I have the misfortune of working on one of them at the moment.

So, I have a registration form. Here is what I need to do:

  1. Validate that all the information is correct server-side
  2. Trigger 2FA protocol to collect and verify user’s phone via Twilio
  3. Tokenize Stripe card details and setup a subscription
  4. Register the user
  5. If all of the above looks good, login the user and redirect them to their profile

And of course of all this is one monolithic form without any steps or session storage. And to add some kerosine to the fire, the whole thing is a plugin-based architecture, where various parts are injected by various plugins that can be enabled and/or disabled.

Sadly trying to make all of this work with native jQuery (or even vanilla DOM) event handlers is just too much.

a) There is no way to ensure that validation handlers are executed before the form is sent to the server, or that the success handler doesn’t fire too early or too late,

b) There isn’t much you can do about promises in handlers — if you need to e.g. talk to the server and wait for its response before moving on to the next handler, i.e. event handlers are synchronous and all you can do is return true or false.

c) It’s hard to handle errors, because of the binary nature of callback return values.

So what do you do?

What if we could do something like this (I am using RequireJS here)?

In the example above we have a page that has a form and has two AMD modules, which are loaded at different times, but they both want to register their handlers on the instance of the form. Our constructor wraps the form and allows us to register onSubmit, onSend, onSuccess, and onError handlers. We want all onSubmit handlers to be asynchronous and we want them all to run before the data is sent to the server. Once all onSubmit handlers resolve successfully, i.e. when we validated all our form data, tokenized our Stripe card input, did our 2FA, we can send the data to the server. onSend handlers are responsible for talking to the server. Once all of them complete asynchronously, we can then execute our onSuccess handlers, i.e. tell the user that data is sent and redirect them to another page. Should there be errors, we want to handle them graciously with onError handlers.

Let’s create our ajax/Form module that will leverage jQuery Deferrable to achieve what we want:

That’s it. I hope you can try it out next time you are scratching walls trying to get legacy jQuery code submit a complex form with asynchronous logic.

--

--

Full-stack developer, passionate about front-end frameworks, design systems and UX.