DotNetCurry Logo

Asynchronous Programming in ES6 Using Generators and Promises

Posted by: Ravi Kiran , on 5/17/2015, in Category HTML5 & JavaScript
Views: 17136
Abstract: ES6 adds the support of promise API to the language. It is also possible to design some asynchronous APIs using generators. In this article, we will explore asynchrony in the next version of JavaScript.

Asynchronous programming has started gaining a lot of attention over the past couple of years. In JavaScript-oriented  applications, there are a number of cases (viz., making an AJAX call or executing a piece of logic after certain time) where we need to deal with asynchronous logic. Some of the JavaScript APIs that were added to browsers as part of HTML5 (viz., IndexedDb, Geo locations and Web sockets) are asynchronous. Despite of such heavy usage of asynchrony, JavaScript as a language doesn’t support it well. Callbacks are the only way one can handle asynchronous APIs in JavaScript; but the approach fails to scale. For example, when you have to deal with more than 3 levels of nested callbacks, the code starts forming a nice looking tree that is hard to read and understand.

There are a number of libraries like Q, RSVP, Bluebird, and APIs in frameworks like jQuery’s deferred API and Angular’s $q API which addresses this issue. Most of these APIs are designed based on Promise/A specification. Though these APIs do a good job of avoiding to deal with callbacks, they add one more entry into the developer’s list of learning. And each of them are different. So if you have to switch from one library to the other, you need to unlearn and relearn. Although unlearning and relearning is not new to developers, it would be certainly better if JavaScript itself had async APIs.

This article is published from the DotNetCurry .NET Magazine – A Free High Quality Digital Magazine for .NET professionals published once every two months. Subscribe to this eMagazine for Free and get access to hundreds of free .NET tutorials from experts

This article is a continuation of the ECMA Script 6 (ES6) articles published earlier in the DNC Magazine. The first part covered the improvements to the JavaScript’s language features and second part covered the new data structures and new APIs added to the existing objects. This article will focus on improvements that make asynchronous programming better in ES6.

ES6 adds the support of promise API to the language. It is also possible to design some asynchronous APIs using generators. In this article, we will see how to use these features individually and together, to deal with asynchrony in JavaScript.

ES6 Generators

Simple Generator Functions

As I stated in my first article on ES6 (ES6: New Language Improvements in JavaScript), generators are used to make objects of classes iterable. They can also be used to create functions or methods that return iterable objects.

For example, let’s write a simple function that returns a specified number of even numbers.

function *firstNEvenNumbers(count){
    var index = 1;
    while(index <= count){
        yield 2*index;
        index++;
    }
}

Now we can call this function with different values of count to produce different number of even numbers on each call. Following snippet shows two instances of calling the above function:

for(let n of firstNEvenNumbers(3)){
    console.log(n);
}

for(let n of firstNEvenNumbers(5)){
    console.log(n);
}

The important thing to notice here is that the execution of the Generator function is paused after yielding a result until the next iteration is invoked. Here the for…of loop implicitly invokes the next iteration. We can manually invoke the next iteration using generator object returned by the function. Following snippet shows the same:

var generator = firstNEvenNumbers(4);
var next = generator.next();
while(!next.done){
    console.log(next.value);
    next=generator.next();
}

Asynchronous Functions using Generators

As we saw, execution of a generator is paused till the next iteration is invoked. This behavior of pausing and continuing can be used to perform a set of asynchronous tasks in which the tasks to be executed depend on result of already finished tasks. We can design a generator function like the one in the following snippet, without using a loop:

function *generatorFn(){
    //first task
    yield firstValue;

    //second task
    yield secondValue;

    //third task
    yield thirdValue;
}

We can pause execution of the above function in between, by calling setTimeout in the statement that uses yield keyword. This causes the statements below the yield keyword to wait till the control is executing the timeout. The following function demonstrates this:

function *simpleAsyncGenerator(){
    console.log("starting...");
    yield pauseExecution(2000);
    console.log("Came back after first pause!");

    yield pauseExecution(5000);
    console.log("Came back after second pause!");

    yield pauseExecution(3000);
}

The pauseExecution function in the above snippet calls setTimeout internally and passes the control back to the generator function by calling the next() method on the generator object. So it needs a reference of the generator object. The following snippet gets a generator object of the above function and implements the pauseExecution method:

var generatorObj = simpleAsyncGenerator();
generatorObj.next();

function pauseExecution(time){
    console.log(`Pausing for ${time}ms`);
    return setTimeout(function () {
        generatorObj.next();
    }, time);
}

The pauseExecution method defined above is not generic; it directly depends on generator object of the simpleAsyncGenerator function. To make it generic, we need to create an object or, array to hold all generator objects and pass name of the property of the generator object wherever it is needed. The following snippet shows this:

function pauseExecution(time, generatorName){
    console.log(`Pausing for ${time}ms`);
    return setTimeout(function () {
        generators[generatorName].next();
    }, time);
}

function *simpleAsyncGenerator(generatorName){
    console.log("starting...");
    yield pauseExecution(2000, generatorName);
    console.log("Came back!");

    yield pauseExecution(5000, generatorName);
    console.log("Came back!");

    yield pauseExecution(3000, generatorName);
}

var generators={}, generatorName="simpleAsyncGenerator";
generators[generatorName] = simpleAsyncGenerator(generatorName);
generators[generatorName].next();

ES6 Promises

What is promise?

The traditional callback approach of handling asynchronous tasks is good for a limited number of nested levels and they can be used in very few layers. Usually, a nested callback model consisting of 3 or more async operations becomes unmanageable. Also, if the application is divided into multiple layers, keeping track of the callback becomes a challenge after certain depth. Promises define a different way of dealing with asynchronous operations and they attempt to reduce these difficulties.

A promise is an object wrapper around an asynchronous operation that gets back with the result (either success or failure) once execution of the operation is finished. At a given point of time, a promise would be in one of the following states:

  • Pending: When execution of the operation is still in progress
  • Success: Once execution of the operation is successfully completed
  • Failure: Once execution of the operation is failed

The promise object accepts callbacks to be executed upon completion of the asynchronous operation. These callbacks in turn return promises; which makes it easier to chain multiple promises.

Now that you got some idea about promises, let’s look at Promises in ES6.

ES6 Promise API

ES6 adds support of Promise to the JavaScript language. To create a promise, we need to call the constructor of the Promise class. Following snippet shows the syntax of a promise handling an asynchronous operation:

var promise = new Promise(function (resolve, reject) {
        //Statements in the function
    
        if(allIsGood) {
            resolve(result.data); //All is well, so resolve the promise
        }
        else {
            reject(result.error); //Something is wrong, reject the promise
        }
    });
});

Here, the imaginary function anAsynchronousFn accepts a callback that will be called after the asynchronous operation is completed.

The parameters resolve and reject passed into the promise function are the callbacks that will be called once the promise succeeds or fails respectively.

We can get result of the above promise by calling the then method on the above promise object.

promise.then(function (data) {
    //Use data
}, function (error) {
    //Handle the error
});

Let’s take an example to understand the above snippets better. Consider the following function. It uses setTimeout to make its behavior asynchronous and uses Promise to emit the result once the operation is completed:

function asyncFunctionUsingPromise(pass){
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            if(pass){
                resolve(100);
            }
            else{
                reject({error: "Some error occured!"});
            }
        }, 2000);
    });
}

Here, the first parameter pass is a Boolean parameter to force the async operation to either pass or fail. Second parameter onSuccess is a callback that will be called once the operation succeeds and the third parameter onFailure is another callback that will be called if the operation fails.

Now let’s consume this function and test the cases of success and failure.

function onSuccess(data){
    console.log("Promise Passed with result: " + data);
    return data;
}

function onFailure(error){
    console.log("Promise Failed!");
    console.error(error);
}

asyncFunctionUsingPromise(true).then(onSuccess, onFailure);

asyncFunctionUsingPromise(false).then(onSuccess, onFailure);

It is not mandatory to pass both callbacks to the then method. We can omit the one that is not going to be used. In most of the cases, we omit the failure callback.

Now let’s see some more API functions that are available through Promise object:

Promise.resolve: Used to create and resolve a promise immediately with a fixed value

Promise.resolve({city: "Hyderabad"}).then(function (data) {
    console.log(data);
});

Promise.reject: Used to create and reject a promise immediately with a fixed error message

Promise.reject({error: "Some error occured!"}).then(null, function (error) {
    console.log(error);
});

Promise.prototype.catch: If it is known that a promise is going to fail, or to avoid handling of success case of a promise, the catch method is used

Promise.reject({error: "Some error occured! (Using catch)"}).catch(function (error) {
    console.log(error);
});

Promise.all: Used to combine an array of promises into one promise. The resulting promise will succeed if all promises are succeeded and it will fail if any of the promises fail

var array = [Promise.resolve(100), Promise.resolve({name:"Ravi"}), Promise.resolve(true)];
Promise.all(array).then(function (data) {
    console.log(data);
});

Promise.race: Accepts an array of promises and results with result of first promise

Promise.race(array).then(function (data) {
    console.log(data);
});

Chaining Promises

One of the advantages of using promises is it is easy to chain them. As the then method returns a promise, we can wire up another asynchronous operation inside the then method and handle its callbacks using a chained then method.

Consider the following asynchronous functions:

function getEmployee(empId){
    var employees = [
        {employeeId: 'E0001', name: 'Alex', department: 'D002'},
        {employeeId: 'E0002', name: 'Jil', department: 'D001'}
    ];
    var emp = employees.find(e => e.employeeId === empId);

    if(emp)
        return Promise.resolve(emp);
    else
        return Promise.reject({error:'Invalid employee id'});
}

function getDepartment(deptId){
    var departments = [
        {departmentId: 'D001', name: 'Accounts'},
        {departmentId: 'D002', name: 'IT'}
    ];

    var dept = departments.find(d => d.departmentId === deptId);

    if(dept)
        return Promise.resolve(dept);
    else
        return Promise.reject({error:'Invalid department id'});
}

Now let’s write a function that would get details of an employee and then get details of the department. So the logic of getting department depends on result of the promise returned by the getEmployee function. Here’s the snippet that does this operation:

getEmployee(employeeId).then(function (employee) {
    console.log(employee);
    return getDepartment(employee.department);
}, function(error){
    return error;
})
.then(function (department) {
    console.log(department);
}, function(error){
    console.log(error);
});

Though the above snippet has an asynchronous task depending on another, it still looks sequential and hence easily manageable. Similarly, if there is a need to perform another asynchronous operation using department object, it can be chained with promise returned from the second then method.

Combining Generators and Promises

Till now we saw how promises and generators can be used separately to compose asynchronous operations. We can use the powers of both of these weapons together to build some advanced and sophisticated asynchronous APIs.

One of the use cases that I can think of where promises and generators play well together is, polling. Polling is a technique using which a client keeps pinging the server for data. The server responds with data when it is available.

We can use a generator to respond with data each time we receive a response from the server. Here as we don’t have a server setup, let’s use Promise.resolve to respond with different results on every poll. The polling function would return promises on every iteration.

Following is a sample polling function that runs five times:

function *polling(){
    var count = 0, val={};
    do{
        count++;
        if(count !== 4){
            yield Promise.resolve({});
        }
        else{
            yield Promise.resolve({value: "Some value"});
        }
    }while(count<5);
}

Now let’s write a recursive function that runs the above function and uses its values. As the polling function returns a generator, we will call the next iteration after every one second.

Following is the runPolling function and its first call:

function runPolling(pollingIterator){
    if(!pollingIterator){
        pollingIterator = polling();
    }

    setTimeout(function(){
        let result = pollingIterator.next();
        if(result.value) {
            result.value.then(function (data) {
                console.log(data);
            });
        }
        if(!result.done) {
            runPolling(pollingIterator);
        }
    }, 1000);
}

runPolling();

If you run this code, it would keep printing results on the console with a gap of one second each.

Generators and promises can also be combined when we need multiple and dependent asynchronous operations to be performed by one function and result of each operation has to be returned to the calling function.

Conclusion

As we saw, Promises and Generators come handy to design and use asynchronous APIs and still make the code look sequential and synchronous. These features help in interacting with any asynchronous browser APIs or any library APIs. Down the line, we may see some of the browser APIs using these features.

Download the entire source code from GitHub at bit.ly/dncm18-es6async

Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+
Further Reading - Articles You May Like!
Author
Rabi Kiran (a.k.a. Ravi Kiran) is a developer working on Microsoft Technologies at Hyderabad. These days, he is spending his time on JavaScript frameworks like AngularJS, latest updates to JavaScript in ES6 and ES7, Web Components, Node.js and also on several Microsoft technologies including ASP.NET 5, SignalR and C#. He is an active blogger, an author at SitePoint and at DotNetCurry. He is rewarded with Microsoft MVP (Visual Studio and Dev Tools) and DZone MVB awards for his contribution to the community


Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!