Skip to content

Instantly share code, notes, and snippets.

@wthit56
Last active December 11, 2015 12:08
Show Gist options
  • Save wthit56/4598163 to your computer and use it in GitHub Desktop.
Save wthit56/4598163 to your computer and use it in GitHub Desktop.

Async Techniques

NOTE: this project is for ideas regarding how to work with asynchronous calls. It includes ways one might achieve different effects and flows, in the form of simple, commented code. It does not include libraries or reusable code of any kind; this is to help programmers understand when to use certain techniques and how such techniques can be implemented.

When working with an asynchronous framework such as Node.js, you will often use code like the following:

Namespace.doSomethingAsync(callback);

The callback function you provide will be called (back) with the requested data, or simply as a notification that the requested process is progressing in some way. What you usually want to do in this callback is to act upon the data received, or to take some action once a certain stage in the process has been completed. And in most cases, this requires knowing something about the origin of the call; what led up to it in the first place.

The simplest way of doing this is to create the callback function in the same place you want to send the async request. This means any data that led to the call being executed will also be accessible to the callback function.

var a = 10;
if(a > 9){
    Namespace.doSomethingAsync(function(){
		console.log(a);
	});
}

However, a callback function would be created every time the async call is sent off. In more real-world cases, these callback functions can be fairly complex, and can quickly clog up memory and, eventually, CPU when the GC (Garbage Collection) algorithm kicks in.

And so, the ideal method would be to create the callback function outside of where you send off async request, and use a reference to the same callback function for each of your async requests.

function callback(){
	console.log(a);
}

// IIFE to demonstrate privately-scoped variables
(function(){
	var a = 10;
	if(a > 9){
		Namespace.doSomethingAsync(callback);
	}
})();

The problem with this is that the re-usable callback function has no access to any private variables (within the scope of a function, in a context other than the one the callback function was created in). So we want the same logic to happen for each async callback, but we also need some way of using the state that led to the async request to base further logic or actions on.

It is this balancing act that this project explores. There are many ways to approach this problem, each with their own issues and provisos. It is the aim of this project to help you understand the options available to you, while helping you decide which technique would work best for you.

To make contextual data available to the re-usable callback function, you will need to create a new function when calling the async method. The trick is to keep this as small as possible, leaving the real callback function to do all the work. Your job is to inject the required data into that function. You can do this in a couple of ways...

The Options

Believe it or not, this "proxy callback" technique is the only way of doing this. All those libraries you can use to make this stuff simpler? They create a proxy function too. There are two ways of getting your data into your "real" callback; closure and context injection.

Closure

The simplest way of doing this is to call the callback function, passing in any required data. This should work most of the time, though if you have a lot of little variables it could be unwieldy to use.

(function () {
	var input = "blah";
	setTimeout(
        function callback(returned) {
            action(input, returned);
        },
        0, "returned value"
    );

    function action(input, returned) {
    	console.group("closure");
		console.log("input: ", input);
		console.log("returned (argument 1): ", returned);
		console.groupEnd();
	}
})();

One thing to note is that you won't be able to edit the original variables themselves unless they are objects (primitive variable types are passed by value rather than reference), and this could lead to unnecessary memory use if you're doing some heavy-duty, performance-critical async calls.

You will also need to pass in any data you may need in the future (as in, for callbacks on future async requests), which could get annoying to remember to do in each related procy callback.

Context Injection

Alternatively you could wrap everything up in a big object that holds all the data you care about. Then, you can use that object as the context for any and all async callbacks. You can update the values as you need to, and even keep track of what "phase" you are up to in processing that particular thread of processes.

(function () {
	var input = "blah";
	setTimeout(
        function callback() {
            action.apply(input, arguments);
        },
        0, "returned value"
    );

    function action(returned) {
    	console.group("inject context");
		console.log("input: ", this.toString());
		console.log("returned (argument 1): ", returned);
		console.groupEnd();
	}
})();

Again, this could bloat the memory usage somewhat, but you will be able to edit that same object for the entire lifetime of that thread of processing.

Conclusion

If you understand the two options above, then you have pretty much all there is to know about using async requests and callbacks. Using them, you can write a scheduling system, an async flow chain, whatever you need to.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment