Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Johnathan Hunt
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Johnathan Hunt ( @JHuntSecurity )

Q's Node Resolver Will Aggregate Results In An Array When Necessary

By on

As I've been experimenting more with Node.js and the MySQL driver, I've been seeing some inconsistent results. Sometimes, my query callback would receive two result parameters (one for the recordset and one for the fields); and, sometimes, the callback would receive a single parameter that was an array containing the two preceding parameters. At first, I though the MySQL driver was performing some sort of introspection of my callback signature. But, I finally realized that it was actually the Q Promise library. It's node resolver will aggregate results into an array when there is more than one value that it has to make available to the promise chain.

If you are a Node.js developer, you are likely familiar with the node-style callback signature:

function( error, results );

Here, the first argument is the error object, which only exists if an error occurred (and is null otherwise). The second argument is the result, which is mutually exclusive with the error. In the majority of cases, there is only one result parameter. But, the MySQL driver will actually provide two results to its .query() callback:

function( error, records, fields );

When converting from a callback-style control flow to a Promise-oriented control flow, we have to turn the error object into a rejected promise and the result object(s) into a resolved promise. We can do this manually; or, we can use Q's deferred.makeNodeResolver() method to generate the callback proxy for us.

NOTE: Q also has a number of other convenience methods for this, like .nfcall() and .nfapply(), among others.

This callback proxy, generated by the .makeNodeResolver() method, will only ever pass one argument down the promise chain. Which means, if the internal callback receives more than one result object, Q will aggregate the results as an array. To demonstrate this, we can try using .makeNodeResolver() will different callback signatures:

// Require our core node modules.
var chalk = require( "chalk" );
var Q = require( "q" );


// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //


// I invoke the given node-style callback with ONE result.
function withOneResults( callback ) {

	callback( null, "A" );

}

// I invoke the given node-style callback with TWO results.
function withTwoResults( callback ) {

	callback( null, "A", "B" );

}

// I invoke the given node-style callback with THREE results.
function withThreeResults( callback ) {

	callback( null, "A", "B", "C" );

}


// For each method above, we're going to use Q's .makeNodeResolver() method. The number
// of results passed to resolver influences the type of data that the resolver passed
// down the promise chain.
[ withOneResults, withTwoResults, withThreeResults ].forEach(
	function tryMethod( method ) {

		var deferred = Q.defer();

		// Q's .makeNodeResolver() will automatically resolve or reject the promise
		// based on what it is passed to it (assuming a node-style callback signature).
		method( deferred.makeNodeResolver() );

		deferred.promise.then(
			function handleResolve( results ) {

				console.log( chalk.red( method.name + ":" ), results );

				return( results );

			}
		);

		// NOTE: We could have also just used "Q.nfcall( method )", which would have
		// given us the same outcome. But, using the .makeNodeResolver() directly makes
		// the demo a bit more explicit.

	}
);

As you can see, we have callbacks that provide one, two, and three result arguments, respectively. When we try to consume those with Q's .makeNodeResolver() method, we end up the following terminal output:

Q's deferred.makeNodeResolver() will aggregate results as an array, when necessary.

As you can see, if the callback receives a single result, Q's .makeNodeResolver() will pass it through, as is, to the promise chain. However, once we have two or more result parameters, .makeNodeResolver() will aggregate them as an array.

NOTE: Q has another convenience method called .spread(), which takes an array and spreads it out as individual arguments to the given callback. It's basically an array-friendly version of .then(). Not to be confused with .all().

Once I realized that this was happening, it made total sense. After all, you can only propagate a single value down through the promise chain. So, in order to make multiple results available, the resolver has to combine them. This behavior just wasn't all that clear from the documentation.

Want to use code from this post? Check out the license.

Reader Comments

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel