Async/Await

Async/Await

Due to the event-driven nature inherent in JavaScript, asynchronous code has always been explicit and verbose, leading to projects which are hard to read and maintain. N4JS already implements the async/await proposal and it provides additional features allowing the use of legacy code in combination with await and promises.

Over the years, growing trends have been to migrate from simple callbacks to Promise objects which is making the chaining of asynchronous data flows easier. This still urges the developer to implement callbacks on the resolution of a Promise.

N4JS implements a new approach proposed by TC39 on async functions to tackle the problem. It conceals the drawn-out details and formal approach so that a developer would have less code to write. A function or method can essentially become an asynchronous function by prepending the keyword async. Inside the function, the developer can call other functions and wait for their results using the keyword async:

async function foo(): int {
    let res = await anotherAsyncFunction();
    ++res;
    return res;
}

The benefit here is that async functions integrate well with any existing Promise-based APIs, i.e. N4JS treats any return type T of an async method or function as a Promise<T, any>.

On the other hand, async code can simply await on functions that return a Promise object:

function fetch(url: string): Promise<Response, any> {
    // WHATWG fetch
    return null;
}

async function foo() {
  let html = await (await fetch('http://www.google.com')).text();
  console.log(html);
}

Error Handling

Since there’s a one-to-one mapping to Promises, exceptions being thrown within an async function call are rejecting the corresponding Promise:

function throwr() { return Promise.reject(new Error('bah!')); }

async function foo() {
    try {
        await throwr();
    } catch (err) {
        (err as Error).message === 'bah!';
    }
}

A rejected Promise will be reflected as an exception being thrown in the async function:

async function throwr() { throw new Error('bah!'); }
let promise: Promise<void, ?> = throwr();

promise.catch(err => (err as Error).message === 'bah!');

"await" for Legacy Code

There’s quite a lot of callback-driven code alive that is not returning Promises, especially with reference to Node.js core modules:

import * as fs from 'fs';

fs.readFile('myFile.txt', (err, content) => {
    if (err) throw err;
    console.log(content);
})

This code follows the general Node.js callback pattern of passing the error as a first argument following the result value(s).

Since N4JS encourages you to use and define types where possible, we can do better here, too. N4JS allows to use the @Promisifiable annotation in this case (and actually the Node.js runtime definitions provided by N4JS already make use of it):

@Promisifiable
export external public function readFile(
  filename: string,
  options: ~Object with {encoding: string?; flag: string?;},
  callback: {function(Error, string)}?): void;

This allows to call the function without implementing any callback, and let the transpiler wire up a Promise for you using the @Promisify annotation:

import { readFile } from 'fs';

function foo(): Promise<string, any> {
    var promise = @Promisify readFile('myFile.txt', { encoding: 'UTF-8' });
    return promise.then(content => content.replace(/foo/g, 'bar'));
}

Since promises integrate with async functions, we could even further condense this down:

import { readFile } from 'fs';

async function foo(): string {
    var content = await readFile('myFile.txt', { encoding: 'UTF-8' });
    return content.replace(/foo/g, 'bar');
}

Finally, it’s noteworthy to mention that arrow functions can work asynchronously as well, which is particularly helpful. The following example demonstrates how we can easily test async code to throw an error using an async arrow expression in the context of the N4JS test framework "mangelhaft".

import { Assert } from 'n4/mangel/assert/Assert';

export public class FooTest {

    @Test async myTest() {
      await Assert.throwsAsync(async () => {
        // async piece of code that is throwing an error
      });
    }

}

Quick Links