Asynchronous Functions

Generator functions have existed in other languages, like Python, for quite some time, so folks have made some clever uses of them. We can combine promises and generator functions to emulate asynchronous functions. The key insight is a single, concise method that decorates a generator, creating an internal "promise trampoline". An asynchronous function returns a promise for the eventual return value, or the eventual thrown error, of the generator function. However, the function may yield promises to wait for intermediate values on its way to completion. The trampoline takes advantage of the ability of an iterator to send a value from next to yield.

var authenticated = Promise.async(function *() {
    var username = yield getUsernameFromConsole();
    var user = getUserFromDatabase(username);
    var password = getPasswordFromConsole();
    [user, password] = yield Promise.all([user, password]);
    if (hash(password) !== user.passwordHash) {
        throw new Error("Can't authenticate because the password is invalid");
    }
})

Mark Miller’s implementation of the async decorator is succinct and insightful. We produce a wrapped function that will create a promise generator and proceed immediately. Each requested iteration has three possible outcomes: yield, return, or throw. Yield waits for the given promise and resumes the generator with the eventual value. Return stops the trampoline and returns the value, all the way out to the promise returned by the async function. If you yield a promise that eventually throws an error, the async function resumes the generator with that error, giving it a chance to recover.

Promise.async = function async(generate) {
    return function () {
        function resume(verb, argument) {
            var result;
            try {
                result = generator[verb](argument);
            } catch (exception) {
                return Promise.throw(exception);
            }
            if (result.done) {
                return result.value;
            } else {
                return Promise.return(result.value).then(donext, dothrow);
            }
        }
        var generator = generate.apply(this, arguments);
        var donext = resume.bind(this, "next");
        var dothrow = resume.bind(this, "throw");
        return donext();
    };
}

As much as JavaScript legitimizes the async promise generators by supporting returning and throwing, now that Promises are part of the standard, the powers that sit on the ECMAScript standards committee are contemplating special async and await syntax for this case. The syntax is inspired by the same feature of C#.

var authenticate = async function () {
    var username = await getUsernameFromConsole();
    var user = getUserFromDatabase(username);
    var password = getPasswordFromConsole();
    [user, password] = await Promise.all([user, password]);
    return hash(password) === user.passwordHash;
})

One compelling reason to support special syntax is that await may have higher precedence than the yield keyword.

async function addPromises(a, b) {
    return await a + await b;
}

By decoupling async functions from generator functions, JavaScript opens the door for async generator functions, foreshadowing a plural and temporal getter, a standard form for readable streams.

results matching ""

    No results matching ""