Synchronizing Asynchronous JavaScript with ES7

Synchronizing Asynchronous JavaScript in ES7ECMAScript version 5 is the latest complete version of JavaScript available and is implemented in every major browser, but ES6 is in the works and promises a much better world for JavaScript developers with features such as a simple class syntax, arrow functions, built-in promises and the like. It doesn't stop there, though; we're already looking ahead to ES7 while ES6 is still cooking in the oven. In particular, I'm extremely excited about asynchronous functions.

The Current Situation, ES6, and Beyond

Before we dive into the asynchronous functions of ES7, let's build up by showing how we currently implement things, how it'll change in ES6, and then show how asynchronous functions just make things even better. First off, we're going to take a look at promises. Promises are a standard laid out in the Promises/A spec. Right now there are dozens of libraries that provide their own implementations of this spec, and most also throw some other features on top. It's great that we have a standard, and the most important functionality is implemented the same across the board, but there are still a lot of inconsistencies between the libraries. It'd be nice if we could settle this down to a single implementation... we have! ES6 is bringing in its own implementation of promises which should take over and become the de facto way of doing things. I'm still on the fence about how I feel about the syntax, but that's not a big issue.

Promises are great and everything, but we still like to write our code synchronously. Whether we're using callbacks out the whazoo or replacing them with promises, it's still harder to read than synchronous code. Well, another great ES6 feature has come to the rescue for us there: generators. Jmar777 talks about generators, giving us a quick rundown, and then goes on and tells us how he created a library that takes advantage of generators to force the code to simply wait until the asynchronous operation is finished before moving to the next line. This works really well and, for now, can be extremely useful.

Here's an example (adapted from the async functions proposal page) of using pure promises vs using promises + Suspend (jmar777's generator library):

// With Pure Promises
function chainAnimationsPromise(elem, animations) {
    var ret = null;
    var p = currentPromise;
    for(var anim in animations) {
        p = p.then(function(val) {
            ret = val;
            return anim(elem);
        })
    }
    return p.catch(function(e) {
        /* ignore and keep going */
    }).then(function() {
        return ret;
    });
}

// With Suspend
function chainAnimationsGenerator(elem, animations) {
    return suspend(function*() {
        var ret = null;
        try {
            for(var anim of animations) {
                ret = yield anim(elem);
            }
        } catch(e) { /* ignore and keep going */ }
        return ret;
    });
}

The magic here is in the suspend(function*()... line and the yield statement. I was blown away when I first saw that we could do this.

ES7's Gift to the Web Developers

Using generators works, but it's a bit of a hack. Generators weren't originally designed for that, even if they serve that purpose well. Instead JavaScript will be receiving a built-in way to halt execution of code as we wait for an asynchronous operation to finish. We do this by using the await keyword inside an async function:

// With Asynchronous Functions and `await`
async function chainAnimationsAsync(elem, animations) {
    var ret = null;
    try {
        for(var anim of animations) {
            ret = await anim(elem);
        }
    } catch(e) { /* ignore and keep going */ }
    return ret;
}

You must use async on the function in order for await to work. Also, notice that when you use await, if the promise is resolved, it will evaluate to the value the promise was resolved with, so you can use a simple assignment like we did in the example. If the promise is rejected, it'll throw an error, which means we can catch rejections with try and catch blocks. Using the await keyword should work with any promise, not just ones returned from another asynchronous function or an ES6 built-in promise.

When we prepend a function declaration with async, it'll return a promise without you having to even touch the promise API! To resolve the promise, just return a value from the function (or don't return any values if you want it to resolve without a value), and if you want to reject the promise, just throw your rejection value.

If you're like me, you might be thinking that this is awesome, but it's not really useful because this feature isn't available yet. Well, that's true, but the traceur compiler actually already supports compiling this feature to ES5, so if you think adding a build step is worth the time you'll save, I'd definitely take a look into it.

Notes

You may have noticed that the asynchronous function example looks very similar to the Suspend example, except we don't need to require a library for it, we don't need the wrapping suspend function, we add the async keyword to the front of the function declaration, and we replace yield with await. There's a reason for this similarity. To quote the spec page:

Async functions are a thin sugar over generators and a spawn function which converts generators into promise objects.

In other words, while I considered the use of generators as a hack, they are still being used behind the scenes; we're just replacing them with a cleaner and clearer syntax specifically designed for promises. The reason why await only works inside an async function is because the async keyword is the signal to the translator to replace it with a spawn/suspend function and swap all the await keywords to yield.

Also, keep in mind that this spec is in the very early stages, so things could change quite dramatically, though I can't see what they'd change except maybe the keywords.

Finally, another great article about this is Jake Archibald's article on ES7 async functions. It's worth checking out.

Conclusion

I used to be excited about ES6, but now I'm more excited about ES7. Promises were supposed to be a solution to the asynchronous operation problem, but they really only solved a small subset of what we needed. I think asynchronous functions from ES7 take promises to the next level and really make asynchronous coding simpler. God Bless and Happy Coding!

About the Author

Author: Joe Zim

Joe Zim

Joe Zimmerman has been doing web development ever since he found an HTML book on his dad's shelf when he was 12. Since then, JavaScript has grown in popularity and he has become passionate about it. He also loves to teach others though his blog and other popular blogs. When he's not writing code, he's spending time with his wife and children and leading them in God's Word.


  • Guest

    nice post