Asynchronous JavaScript on the client
Asynchronous development is very important for our applications’ performance and responsiveness. An application that is written synchronically will be “stucked” every time it needs to perform a “long running operation” like network operations, file access, etc. In languages such as Java, C# or C++ asynchronous development is done by threads. When we need to perform a “long running operation” we use a thread and achieve asynchronization. In JavaScript, asynchronous development is one of the most confusing part of the language.
In this blog post I will discuss and describe asynchronous JavaScript development.
JavaScript does one thing at a time. Or does it?
Many developers regard JavaScript programs as single-threaded. JavaScript uses an event driven programming model. The web browser trigger events, such as mouse events, keyboard events, DOM events, etc. The event is queued into the event loop. Each time, the JavaScript thread takes the next event and executes it by activating its event handler. When it finish to handle the event, it takes the next one. The developer can associate an event handler to the event by register a function to the event.
btn.addEventListener("click", function(){
alert(“Clicked!”)
})
The alert(“Clicked!”) will be executed by the browser when the ‘click’ event will be handled.
This is pretty straightforward, but what happens if the logic that associated to the event is “long running” such as fetching a JSON from the server?
In this case, the browser exposes a set of asynchronous functionalities. The commons are network operation such as HTTP requests, local storage operations etc. The web browser uses a C++ libraries (Usually) that uses threads and run the “long running” operation on them. When the operation is over it will trigger an event (success or failure), the browser (through the event loop) will execute the attached functionality. Many asynchronous objects allow to attach a callback to the execution of the function. The callback is a function that will be called as the operation ended. Implementation of a callback looks like this:
function AsyncCall(callback) {
// create the async object
var worker = new worker();
// register on the 'finish' event
worker.onfinish = function(){
// run the callback
callback(worker.response);
}
// run the workload
worker.doWork();
}
The execution of the function will look like this
asyncCall(function(){
alert(“finish!);
})
Sometimes we need to do different functionality when the operation fails. In this case we will pass the async function two callbacks, one for success, in the other for failure.
Callback hell
Using callbacks can caused a different type of problem. The code can become messy. Reviewing, debugging and fixing the code becomes a very hard thing to do. It even got the name “Callback hell”. Assuming we need to do a series of operations. Each operation depends on the success of its previous. If one of the operations fails, we have to do something else like alerting the client. Our code will look like a spaghetti.
asyncCall(function(response) {
If (response.status == 200) {
calculateResult(function(result) {
If(result.status == OK) {
loadFile(function(res) {
If(res == success) {
doSomthing();
} else {
alert(“file error”)
}
}
}
else {
alert(calculation error”)
}
} else {
alert(“API error”)
}
}
}
That was the incentive for the creation of Promise.
Promises
Promise is a javascript object that is used for deferred and asynchronous computations. A Promise represents an operation that hasn’t completed yet, but is expected in the future. Promises is a pattern that exists in the web development for a long time. You can find it in Q or in JQuery deffered, and more. In ECMA6 (ES2016), it has become a officially a part of javascript. The standard for JavaScript Promises is called Promises/A+.
Promises in practice
Creation of a promise:
var promise = new Promise(function(resolve, reject) {
doTheWork();
if (response == success) {
resolve(response);
} else {
reject(response)
}
The promise constructor receives one parameter which is a function that has to callbacks: ‘resolve’ - for success, and ‘reject’ - for failure. The promise will run its workload, and afterward, in case of success, it will call the ‘resolve’ callback. In case of failure it will call the ‘reject’ callback.
Usage of a promise
promise.then(function(result){
alert("success");
}, function(result) {
alert("failed");
})
When activating the promise, it will be done asynchronously, without blocking the application. When it is done it will call the corresponding callback according to the result of the load. So much cleaner!
The promise has 3 states:
- pending: initial state, not fulfilled or rejected.
- fulfilled: meaning that the operation completed successfully.
- rejected: meaning that the operation failed.
It can only succeed ones or rejected once.
Cascading and multiplicity
Another great feature of Promise is cascading. Promise enables to connect promises one after the other.
promise.then(function(result){
// success
}, function(result) {
//failure
}).then(..).then(...).
Moreover, promise has another method - catch. It is actually a then method without the resolve part:
then(undefined, function(response){
alert(“error”)
}
If I will chain in promises, without the ‘reject’ part, and end it with a catch, I will have a clear and understandable error handling:
promise.then(..).then(..).then(...).catch(...)
Of Course I can use the catch wherever I would like in the chain.
Another feature of Promises is to run multiple promises together by creating an array of promises. You can run them parallel or in a defined sequence.
Promise.all([promise1, promise2, promise3] ).then(function(results){
})
Compatibility
Promises are supported by all new web browsers on desktops and on mobile. But they are not supported on old versions. In order to be able to use wildly you can use a Polyfill such as es6-promise.js and include it as a script.
Other type of asynchronous development
In HTML5, another feature had been exposed - Web Workers, which allowed to run a script in the background, it is very useful for running long scripts without blocking the application. Web workers has some restrictions as not being allowed to access the DOM. The worker execute its code and then it can post a message to the event loop that it finished.
Conclusion
Asynchronous development is very important for keeping the application responsive and use the computer resources efficiently. There are many ways to achieve asynchronization in JavaScript: using web API’s, promises, other libraries like JQuery or Async.js and more. The main thing is to understand the concept rather than be attached to the tool. Because asynchronous development can be very complex to maintain and read, it is a good practice to abstract the implementation by using promises or use named functions. What’s next? In ES7 new async feature will be added to the spec called ‘Async/Await’ (like in C#) which will abstract the language even more. It is not supported yet, and if you want to use you will have to do it with a transpiler.
References:
- JavaScript: The Definitive Guide, 6th Edition by David Flanagan
- http://www.html5rocks.com/en/tutorials/es6/promises/