ES7 introduces async functions, which allow you to write asynchronous code with a synchronous syntax. Unfortunately this doesn't work well with AngularJS: awaited code will run outside of Angular's digest loop, and therefore it won't trigger watchers or view updates.

The problem

ES7 introduces async functions that allow to write async code with a synchronous syntax. This looks like this:

async function() {
	await doSomethingAsync();
	console.log("Something is done!");
	await doSomethingElseAsync();
	console.log("Something else is also done!");
}

This works because await expects a promise as arguments, and it waits until the promise is resolved before continuing.

Unfortunately this doesn’t work well in AngularJS: awaited code is not run in the digest loop, and therefore we need to put $scope.$apply calls all over the place. This results in code like this:

async function initialize() {
	$scope.user = await users.getActiveUser();
	$scope.$apply();
	$scope.messages = await conversations.getMessagesFor($scope.user);
	$scope.$apply();
}

Obviously this isn’t ideal, it would be better if we could omit the $scope.$apply calls.

Meet $async

Fortunately there is a way out. Thanks to the generator functions introduced in ES6 we can write our own async engine that does work well with AngularJS. At Magnet.me we’ve created the $async module which does exactly that. With $async we can rewrite the initialize function like this:

const initialize = $async(function*() {
	$scope.user = yield users.getActiveUser();
	$scope.messages = yield conversations.getMessagesFor($scope.user);
});

Instead of an async function we now use a generator function passed to $async, and instead of awaiting a promise we now yield a promise.

How it works

Generator functions do not run to completion when they are invoked. Instead they return an iterator that can be iterated over manually. This iterator will stop and “return” at every call to yield. For example, the following generator function generates all positive integers:

function* integersGenerator() {
	let i = 1;
	while (true) {
		yield i;
		i++;
	}
}

const integersIterator = integersGenerator();
integersIterator.next().value; //1
integersIterator.next().value; //2
integersIterator.next().value; //3
//etc.

The $async module makes use of the ability to pause generator functions to emulate async functions. It expects a generator that will generate promises, and it will chain all these promises together correctly. The result is that each yield call will wait for its promise to be resolved before continuing. The core of $async looks something like this:

//This function iterates asynchronous over an iterator
function iterateAsync(iterator) {
	//First run until the first yield. 
	//`.value` should now contain the yielded promise
	return iterator.next().value
		//Then we wait until the promise has resolved, 
		//and recursively iterate again
		.then(() => iterateAsync(iterator));
}

//usage:
iterateAsync(function*() {
	yield somePromise;
	yield someOtherPromise;
	//etc.
});

//This is effectively the same as this:
somePromise
	.then(() => someOtherPromise())
	.then(/*etc*/);

Now that we have our own async engine it is trivial to make it work properly with Angular: we can add the call to $scope.$apply in the iterateAsync function and there it is: an async function that works with AngularJS!

The entire module is only a couple of lines long, if you’re interested in all the details then check it out here!

Inspiration

To give credit where credit’s due, the idea of using generators to implement aysnc functions isn’t new. Libraries that do so exist for a long time, with the most well-known being co. $async is mostly inspired by co, but more lightweight and Angular-flavored.