Using jQuery's Pipe() Method To Chain Asynchronous Validation Requests

Posted November 21, 2011 at 11:07 AM by Ben Nadel

Tags: ColdFusion, Javascript / DHTML

The other day, I looked at using Stripe.com to accept PCI-compliant online payments without a merchant account. In that experiment, I performed a cross-domain AJAX request to validate the credit card (using Stripe.com's API) followed by a local AJAX request to validate the rest of the form data. To do this, I put the launch of the second AJAX request inside the success callback of the first AJAX request, manually chaining the dependent requests. This felt a bit sloppy, given the tools we have to work with today; as such, I wanted to see if I could use jQuery's .pipe() method to chain serial, asynchronous requests more elegantly.


 
 
 

 
  
 
 
 

Keeping with the credit card validation motif, I'm going to simulate a scenario in which we have three levels of possible failure during a form submission:

  1. Credit card validation.
  2. Local-server validation.
  3. Database insert.

NOTE: HTTP requests may also fail, but that will be handled explicitly by the AJAX callback methods.

Furthermore, since the credit card validation happens on a "third party server" (or rather, it did in my previous blog post), we don't have control over the response structure. As such, the credit card validation response will not be the same as the local server validation and insertion responses, respectively. We will have to use the chained callbacks to transform the credit card response, creating a uniform response structure.

Before we look at how these requests can be chained together using jQuery's pipe() method, let's look at the server-side code that will be producing the random responses for the demo.

credit_card.cfm (Our Credit Card Validation)

  • <!---
  • Create a default return value. Note that when a credit card
  • succeeed, there is no ERRORS key. We are doing this to simulate
  • non-symetric response structures for credit card validation.
  • --->
  • <cfif (randRange( 1, 10 ) % 2)>
  •  
  • <!--- Successful validation. --->
  • <cfset response = {} />
  • <cfset response[ "success" ] = true />
  •  
  • <cfelse>
  •  
  • <!--- Failed validation. --->
  • <cfset response = {} />
  • <cfset response[ "errors" ] = [] />
  • <cfset response.errors[ 1 ] = "Credit card failed to validate." />
  •  
  • </cfif>
  •  
  •  
  • <!--- Return a 200 response even if we have errors. --->
  • <cfheader
  • statuscode="200"
  • statustext="OK"
  • />
  •  
  • <!--- Return the API response. --->
  • <cfcontent
  • type="text/x-application-json"
  • variable="#toBinary( toBase64( serializeJSON( response ) ) )#"
  • />

When the credit card is validated, notice that the response doesn't have a consistent structure; if it succeeds, it has a "success" key; if it fails, it has an "errors" key. This will be different than the API responses that we have control over (given their location on our local server).

validation.cfm (Our Local Server Form Validation)

  • <!--- Create a default return value. --->
  • <cfset response = {} />
  • <cfset response[ "success" ] = true />
  • <cfset response[ "errors" ] = [] />
  •  
  • <!--- Return a 200 response. --->
  • <cfheader
  • statuscode="200"
  • statustext="OK"
  • />
  •  
  • <!--- Return the API response. --->
  • <cfcontent
  • type="text/x-application-json"
  • variable="#toBinary( toBase64( serializeJSON( response ) ) )#"
  • />

Our local form validation will always return a successful response for this demo. If the credit card validation succeeds, so will this step of the process. The next point of potential failure will come at the database level.

insert.cfm (Our Database Insertion Request)

  • <!--- Create a default return value. --->
  • <cfset response = {} />
  • <cfset response[ "success" ] = true />
  • <cfset response[ "errors" ] = [] />
  •  
  •  
  • <!--- Randomly decide to fail on insert. --->
  • <cfif (randRange( 1, 10 ) % 2)>
  •  
  • <cfset response.success = false />
  • <cfset response.errors[ 1 ] = "Unexcepted database error." />
  •  
  • </cfif>
  •  
  •  
  • <!--- Return a 200 response even if we have errors. --->
  • <cfheader
  • statuscode="200"
  • statustext="OK"
  • />
  •  
  • <!--- Return the API response. --->
  • <cfcontent
  • type="text/x-application-json"
  • variable="#toBinary( toBase64( serializeJSON( response ) ) )#"
  • />

Notice that the local server validation and the local database insert both return a uniform structure. Notice also that they return a "200 OK" response even if the server-side action fails. I'm keeping everything "200 OK" to simplify the demo. I've already examined the jQuery pipe() method as a means to change the Deferred resolution at runtime; but for this exploration, I didn't want to add the complexity of using appropriate HTTP status codes to define API responses.

Now that we see what the three levels of possible failure our, let's take a look at the code that pulls it all together in one serialized validation chain.

  • <!DOCTYPE html>
  • <html>
  • <head>
  • <title>Using Pipe() To Chain Asynchronous Validation</title>
  •  
  • <script type="text/javascript" src="../jquery-1.7.js"></script>
  • <script type="text/javascript">
  •  
  •  
  • // Get the remote credit card validation.
  • var authorization = $.ajax({
  • type: "post",
  • url: "./credit_card.cfm",
  • data: {},
  • dataType: "json"
  • });
  •  
  •  
  • // Chain the local validation to the credit card validation.
  • var localValidation = authorization.pipe(
  • function( response ){
  •  
  • // Check to see if the credit card validated.
  • // If so, then we want to proceed with our
  • // local, server-side validation.
  • if (response.hasOwnProperty( "success" )){
  •  
  • // Return the next validation promise.
  • return(
  • $.ajax({
  • type: "post",
  • url: "./validation.cfm",
  • data: {},
  • dataType: "json"
  • })
  • );
  •  
  • } else {
  •  
  • // Validation failed, return error response, but
  • // translate into a normalized response sturcture.
  • return({
  • success: false,
  • errors: response.errors
  • });
  •  
  • }
  •  
  • }
  • );
  •  
  •  
  • // Chain the local validation to the insert.
  • var save = localValidation.pipe(
  • function( response ){
  •  
  • // Check to see if the local validation succeeded.
  • // If so, we'll try to insert the record.
  • if (response.success){
  •  
  • // Return the next validation promise.
  • return(
  • $.ajax({
  • type: "post",
  • url: "./insert.cfm",
  • data: {},
  • dataType: "json"
  • })
  • );
  •  
  • } else {
  •  
  • // Validation failed, return error response.
  • return( response );
  •  
  • }
  •  
  • }
  • );
  •  
  •  
  • // Validate the entire chain.
  • save.then(
  • function( response ){
  •  
  • // Show validation results.
  • console.log( "Success:", response.success );
  • console.log( "Errors:", response.errors );
  •  
  • },
  • function( resolution ){
  •  
  • // Something went really wrong!!
  • console.log( "Something went wrong in a bad way." );
  • console.log( resolution );
  •  
  • }
  • );
  •  
  •  
  • </script>
  • </head>
  • <body>
  • <!-- Left intentionally blank. -->
  • </body>
  • </html>

This validation chain starts with the credit card validation. This response is then pipe()'d into the local validation request, which is then pipe()'d into the database insert request. Each call to jQuery's pipe() method either passes-on the failed response or, returns a XHR Promise for the next step in the validation chain. Not only does this allow our asynchronous requests to be performed in serial, it provides us with an encapsulated means to normalize the response structures at each step; notice that if the credit card validation fails, the error-only response is transformed into a structure that has both a "success" and "errors" key-set.

The nice thing about the jQuery Deferred's pipe() method is that you can return either an object or another Deferred object; this allows each step of the validation chain a chance to throw the entire chain into a "rejected" resolution, if necessary. This gives our final success and fail handlers a uniform way to react to any point of failure within the entire validation lifecycle.

Most of the time, we don't care that events are asynchronous; but, if one asynchronous event depends on the response of another asynchronous event, things get more complicated. We can end up writing "boomerang" code that nests one callback inside another callback inside another callback (etc.). By using the jQuery Deferred's pipe() method, we start to serialize our asynchronous actions in a way that keeps our code much more readable and elegant.




Reader Comments

Nov 21, 2011 at 12:14 PM // reply »
1 Comments

Thanks Ben. I wasn't even aware of the deferred.pipe() method and its been available for almost a year now. I am excited to use this method to clean up some old sloppy code of mine. Keep up the great posts!


Nov 21, 2011 at 1:14 PM // reply »
10,743 Comments

@Andrew,

Cool man! The Deferred stuff, in general, is pretty awesome. Even if you use nothing more than AJAX calls, I find the way the callbacks get defined to be very pleasing - much more so than putting the success/error callbacks in the AJAX config object.


Jan 31, 2012 at 10:47 AM // reply »
10,743 Comments

@All,

I've expanded on this point approach, wrapping it up into a more structured jQuery plugin called $.validate():

http://www.bennadel.com/blog/2322-Using-jQuery-Deferred-To-Chain-Validation-Rules-In-An-Asynchronous-Non-Blocking-Environment.htm

This uses function callbacks and Deferred objects for each step of the validation process, allowing for asynchronous short-circuiting of the validation process.


Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
InVision App - Prototyping Made Beautiful With Prototyping Tools Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 21, 2012 at 1:58 AM
Updated: Converting A ColdFusion Query To CSV Using QueryToCSV()
Hi Ben, why do you need to have so many double quotes when adding the field and field name to the row data? ----------------------------------------- <cfset LOCAL.RowData[ LOCAL.ColumnIndex ] = ... read »
AXL
May 21, 2012 at 1:24 AM
URL Rewriting And ColdFusion's WriteToBrowser Image Functionality (CFFileServlet)
@Mounir, Open your lower case URL Rewrite rule and add the following condition. Condition input: {REQUEST_URI} Check if input string: Does Not Match the Pattern Pattern: ^/CFFileServlet/_cf_ca ... read »
May 20, 2012 at 4:28 AM
Understanding The Complex And Circular Relationships Between Objects In JavaScript
@Will Vaughn I tried your javascript example but got this error:- foo.print is not a function ... read »
May 19, 2012 at 5:37 AM
A Graphical Explanation Of Javascript Closures In A jQuery Context
Thanks for this article, but I fear you missed an important point. If variables in the outer context change, these changes affect the inner anonymous functions as well. That means: if you change the ... read »
May 18, 2012 at 3:39 PM
Parsing CSV Data With An Input Stream And A Finite State Machine
Can you use file upload button with this? and read live? or does the file have to already be on the server saved? ... read »
May 18, 2012 at 1:06 AM
VIRGO (Aug. 23-Sept. 22): Dead On The Money!
A friend of mine and I were arguing about astrology and she told me that he believes in astrology. She hasn't provided me with any evidence that the belief makes any sense to me. She she been telling ... read »
May 17, 2012 at 11:32 PM
Using ColdFusion to Handle 404 Errors (Page Not Found) On Development Server
Very easy the configuration. I read a lot pages and I can't find the solution. I open the administrator and change this Administrator/server settings/Error Handlers/Missing Template Handler and p ... read »
May 17, 2012 at 3:13 PM
LOCAL Variables Scope Conflicts With ColdFusion Query of Queries
I never cease to be amazed that almost EVERY random CF issue I come across lands me on your site. Thank you for documenting your findings for the world. ... read »