ES6 Callbacks, Promises, and Generators

This is the part of a series of blogs on the new features on the upcoming ECMAScript 6 (ES6) specification which JavaScript implements. However this post will not be as focused on ES6 as my other posts on ES6 as we aim to also cover Callbacks and Promises.

JavaScript has a single threaded runtime, at least for now. So it makes use of a single thread to fulfill all of the application’s work. This is why its asynchronous property allows you to start something that completes later on. This is useful for handling work that would require a lot of processing time such as calls to a webservice, waiting for a CSS transition, or a timeout timer.

Callback #

Callbacks are a way to handle the load of having to deal with a long running process. Here we execute another function after we get the result from calling an AJAX call. The Callback Hell happens when we keep nesting callbacks upon callbacks until the callback tree just becomes unmanageable with no common pattern.

Callback Hell:

$.ajax({
    type: "GET",
    url: "test.json",
    success: function(data) {
        // do something
        $.ajax({
            type: "GET",
            url: "test2.json?id="+data.id,
            success: function(data2) {
                // More callback hell
            },
            error: function(xhr, status, error) {
                $("#errorBar2").append(
                "<span>"+error.toString()+"</span>");
            }
        });
    },
    error: function(xhr, status, error) {
        $("#errorBar").append(
        "<span>"+error.toString()+"</span>");
    }
});

A number of things is wrong with this example such as repeating the code for error situations as well as having crazy nested functions. Fortunately there is a more elegant way of doing callbacks so that it makes it easier for other devs to read your code.

Cleaner Callback Hell:

$.ajax({
    type: "GET",
    url: "test.json",
    success: testTwoWithId,
    error: handleError
});

function testTwoWithId(data) {
    // do something
    $.ajax({
        type: "GET",
        url: "test2.json?id="+data.id,
        success: anotherFreakingFunction,
        error: handleError
    });
}

function anotherFreakingFunction(dataAgain) {
    // do something again
}

function handleError(error) {
    $("#errorBar").append("<span>"+error.toString()+"</span>");
}

Promises - sometimes called Deferred #

A Promise represents a future value. It is the standard syntax for anything that has a delayed response. Most frameworks have something similar to Promises (or a Deferred object) which implements resolve or reject functions. The Promise object is then “then-able” which would respond to resolve or reject events depending on the result. It sounds pretty similar to a traditional Callback function but where Promises make sense is when you have nested Promises/Callbacks. Promises gives you a nice abstraction to manage the complexity.

var test = $.ajax({
    type: "GET",
    url: "test.json"
});

test.then(function(data) {
    return $.get("test2.json?id=" + data.id);
}).then(function(dataAgain) {
    var pos = sampleFunction(dataAgain);
    return $.get("test2.json?id=" + dataAgain.arr[pos].id)
}).then(anotherFreakingFunction(dataYetAgain), function(xhr, status, error) {
    // Handle errors for any of the actions
    handleError(error);
});

function anotherFreakingFunction(dataYetAgain) {
    console.log(dataYetAgain);
}

function handleError(error) {
    $("#errorBar").append("<span>"+error.toString()+"</span>");
}

Promise Flow:

Generators (ES6 only) #

Functions that are generators are denoted passed by a star * such as function*(). We can return results using the yield keyword. The function stops invoking at the yield and then resumes again at the next invocation of the next() call from where we last stopped.

Run..Stop..Run

Generator Basics:

var genFunc = function*() {
    var value = yield 1;
    value; // becomes {test: "testy"} after the second next function is invoked
    yield 2;
}

var gen = genFunc();
gen.next(); // {value: 1, done: false}
gen.next({test: "testy"}); // {value: 2, done: true}
gen.next(); // {value: undefined, done: true}

We can use generators to completely give our Promises structure a nice standard layout of easily understandable async js. The resolved value of $.get() gets sent to var data, dataAgain, dataYetAgain at their respective times when the async code has completed. We also stop just before each yield call so we get the data in order.

// Using Bluebird for coroutine
Promise.coroutine(function* () {

    var data = yield $.get("test.json");
    // run code that uses data

    var dataAgain = yield $.get("test2.json?id="+data.id);
    // run code that uses dataAgain

    var dataYetAgain = yield $.get("test2.json?id=" + dataAgain.arr[pos].id);
    // run code that uses dataYetAgain

    console.log(data, dataAgain, dataYetAgain);
})().catch(function(error) {
    handleError(error);
});

Javascript generators allows you to structure your code seemingly like synchronously, but behind the scenes they work asynchronously.

 
51
Kudos
 
51
Kudos

Now read this

Enhancing JavaScript Arrays: Good or Bad?

This is a way to give JavaScript Arrays some extra functionality to make life easier for development and in turn lead to clear and concise code. I’ve mentioned some ways to give backwards compatibility functionality (otherwise known as... Continue →