Here's why your JavaScript code isn't executing properly
And what you can do about it
JavaScript is a tricky language. That's probably an understatement for anyone that's spent a lot of time with it. But there are a lot of quirks to the language that can trip up people trying to learn it.
One especially difficult part of the language has to do with how it executes. Rather than the interpreter always running one line at a time in sequence, like most people would expect, it sometimes executes different lines of the code at different points. This is what people mean when they say that JavaScript is asynchronous.
But, why is it important to know how your program runs? Because if you don't pay attention to the order of execution, you might end up breaking your code and not having any idea why.
var send = function (data) {
// does something with data
// then passes data along to another function
anotherFunction(data);
};
var provideData = function (input) {
var data;
calculate(input, function(error, result) {
// after function does something with 'input' we save
// the result to data
data = result;
});
send(data);
};
In this example, you have a function, send(data) that depends on receiving data after calculate() has been called. The function send(data) can only run properly if it receives the right input. The problem you eventually discover is that it never does because the function that supplies the data hasn't even calculated what data is supposed to be by the time send(data) is executed.
These are the exact problems that crop up all the time with Node.js and jQuery.
What is asynchronous execution?
To make it clearer what async execution means, consider the following example:
function chaos () {
setTimeout(function() {
console.log('When did I run?');
), 1000};
}
chaos();
console.log('Am I first?');
/*
The order of execution is as follows:
chaos() --> steps into the function but doesn't return anything
right away
setTimeout() --> invokes but doesn't run the anonymous function yet
console.log('Am I first?') --> displays this first
the anonymous function --> the one that was passed to setTimeout()
console.log('When did I run?')
*/
In this example, JavaScript's native function setTimeout is actually invoked immediately, but the anonymous function we passed to it doesn't get invoked until after 1 second (1000 milliseconds) has passed. Only when that function is called will console.log() be invoked. While this is a very simple example, it helps to illustrate the pattern that makes asynchronous code possible in JavaScript: the fact that the language uses callbacks.
How do callbacks affect my code?
A callback is a function that is passed as an argument to another function. Usually, the callback function is only invoked after certain things have already happened within its containing function. What makes callbacks tricky is that many times they might have to be passed certain arguments from their containing function in order to work properly.
As I mentioned before, this is a pattern that is utilized often in jQuery. Consider how we set up event handlers.
$('button').on('click', function() {
// do something
});
When I first learned jQuery, it didn't hit me that methods like .on were really functions that had to take arguments like 'click' in order to run the anonymous function. Yet, that is exactly how jQuery works. It uses callbacks so that it can take advantage of web applications where you might not know exactly when a certain function needs to be invoked.
In fact, callbacks are extremely useful in JavaScript precisely because they are often used to avoid problems with asynchronous execution. Instead of ending a function with a return statement, you can invoke a callback with the result. This may not seem intuitive, but when you are working with nested functions, it sometimes is best to try to pass something directly along rather than trying to get the arguments from a different source.
But, as I showed earlier with the send(data) example, you can still run into problems with callbacks. How do you solve that?
Use Promises to Ensure that Your Code Executes Properly
To fix the first example with the send function, we can use promises. In JavaScript, a Promise is an object used for asynchronous operations. It provides an element of synchronous execution in an application that otherwise operates asynchronously.
By using a promise in the send(data) example, we can update the data variable and make send(data) wait to be executed only after data has a value. Let's see how this would work:
var provideData = function (input) {
var data;
var dataPromise = new Promise(function(resolve, reject) {
calculate(input, function(error, result) {
if (error) {
throw error;
}
else {
data = result;
// only when resolve receives the calculated result
// can the next function in the promise chain execute
resolve(result);
}
});
});
// then() is a method used by promises to execute the next function
// in the chain only once the previous function has run
dataPromise.then(send(data));
};
For clarification, promises are included with the jQuery and bluebird libraries. Bluebird is the one you want to use if you're using Node.js.
That note aside, it now seems like there's a lot that happening in our code, but it's actually pretty simple. All we did is wrap the calculate() function in a promise. The promise, named dataPromise, takes a callback that uses resolve and reject as its arguments. Resolve is a function that takes a value. That value must be passed into resolve before the then method can be called.
The net effect is that only once data is defined and the promise is resolved can send(data) be executed. This will very neatly solve our issue.
While promises can seem like a lot to tackle at first, I would recommend trying them out, especially if it seems like you might be running into errors with asynchronous execution.
Rapper turned Soldier turned Software Engineer
