What about Async/Await?

We’ve heard your feedback that you’re excited about async/await in TypeScript. Async/await allows developers to write to asynchronous code flows as if they were synchronous, removing the need for registering event handlers or writing separate callback functions. You may have seen similar patterns in C#. TypeScript’s async/await pattern makes use of Promises, much like C#’s async/await pattern leverages Tasks. Promises are objects that represent ongoing asynchronous actions and are a built-in feature of ECMAScript 6 (ES6). TypeScript’s async/await is implemented as proposed for ES2016 (aka ES7).

We’re happy to announce that you can already use async/await today if you’re targeting Node.js v4 or later! In this post, we’ll show you how and give you an update on async/await’s progress.

How async/await works

JavaScript is single-threaded and sequential: once your function starts running, it can’t be interrupted before it runs to completion. For most tasks, this is exactly what the developer expects and wants. However, when an asynchronous task (such as a call to a web service) is running, it’s more efficient to allow the rest of the JavaScript to continue running while you wait for the task to return. Async/await allows you to call asynchronous methods much the same way you’d call a synchronous method, but without blocking for the asynchronous operations to complete.

For example, in the code below, main awaits on the result of the asynchronous function ping. Because main awaits, it’s declared as an async function. The ping function awaits on the delay function in a loop, so it’s declared as async as well. The delay function calls setTimeout to return a Promise after a certain amount of time. When setTimeout’s promise returns, you’ll see ‘ping’ on the console.

async function main() {
 await ping();
}

async function ping() {
 for (var i = 0; i < 10; i++) {
  await delay(300);
  console.log(“ping”);
 }
}

function delay(ms: number) {
 return new Promise(resolve => setTimeout(resolve, ms));
}

main();

 

TypeScript uses ES6 generators to implement the ability to re-enter a function at a given point when an asynchronous call returns. Generators use the yield keyword to tell the JavaScript runtime when control is being given up while a function waits for something to happen. When that something happens, the JavaScript runtime then continues the function execution from where control was yielded. For the sample above, the TypeScript compiler emits the below ES6 JavaScript for the ping function.

function ping() {
 return __awaiter(this, void 0, Promise, function* () {
  for (var i = 0; i < 10; i++) {
   yield delay(300);
   console.log(“ping”);
  }
 });
}

The__awaiter function wraps the function body, including the yield statement, in a Promise that executes the function as a generator.

Trying it out with Node.js

Starting with nightly builds, TypeScript 1.7 now supports async/await for ES6 targets. You can install the latest nightly build of TypeScript using npm install typescript@next and try it with Node.js v4 or beyond, which has support for ES6 generators. Here’s how your tsconfig.json would look like:

"compilerOptions": {
  "target": "ES6",
  "module": "commonjs"
}

The compiled JavaScript output can then run in Node.js:

If you’re targeting Node.js v4 or later, try out async/await today. We’ve created a more complex sample using the GitHub API to asynchronously retrieve history on a repo. You can find the source on the TypeScriptSamples repo and run it in Node.js. We’d love to hear your feedback and if you find issues, please let us know. One other thing to keep in mind: Node.js doesn’t yet support ES6 modules, so choose CommonJS module output when compiling your TypeScript, as indicated in the tsconfig.json above.

Next steps

We know that many TypeScript developers want to write async/await code for browsers as well as for Node.js and understand that using a temporary solution based on additional transpiliation layer is not optimal either from a developer workflow perspective or due to the performance impact it causes with the extra compilation overhead. To target the breadth of browsers, we need to rewrite ES6 generator functions into ES5-executable JavaScript using a state machine. It’s a big challenge that requires significant changes across the compiler, but we’re working on it. Stay tuned; we’ll keep you up to date on our progress!