ES7 async functions

They're brilliant. They're brilliant and I want laws changed so I can marry them.

Async with promises

In the HTML5Rocks article on promises, the final example show how you'd load some JSON data for a story, then use that to fetch more JSON data for the chapters, then render the chapters in order as soon as they arrived.

The code looks like this:

function loadStory() {
  return getJSON('story.json').then(function(story) {
    addHtmlToPage(story.heading);

    return story.chapterURLs.map(getJSON)
      .reduce(function(chain, chapterPromise) {
        return chain.then(function() {
          return chapterPromise;
        }).then(function(chapter) {
          addHtmlToPage(chapter.html);
        });
      }, Promise.resolve());
  }).then(function() {
    addTextToPage("All done");
  }).catch(function(err) {
    addTextToPage("Argh, broken: " + err.message);
  }).then(function() {
    document.querySelector('.spinner').style.display = 'none';
  });
}

Not bad, but…

This time with ES7 async functions…

async function loadStory() {
  try {
    let story = await getJSON('story.json');
    addHtmlToPage(story.heading);
    for (let chapter of story.chapterURLs.map(getJSON)) {
      addHtmlToPage((await chapter).html);
    }
    addTextToPage("All done");
  } catch (err) {
    addTextToPage("Argh, broken: " + err.message);
  }
  document.querySelector('.spinner').style.display = 'none';
}

With async functions (full proposal), you can await on a promise. This halts the function in a non-blocking way, waits for the promise to resolve & returns the value. If the promise rejects, it throws with the rejection value, so you can deal with it using catch.

Edit: I originally used await within an arrow function, apparently that's not allowed so I've replaced it with a for loop. Domenic gave me a knowledge smack-down on why await can't be used in arrow functions.

loadStory returns a promise, so you can use it in other async functions.

(async function() {
  await loadStory();
  console.log("Yey, story successfully loaded!");
}());

Until ES7 arrives…

You can use async functions and other ES6/7 features today using the Traceur transpiler. Also, you can use ES6 generators to create something akin to async functions.

You need a small bit of library code, a spawn function. Then you can use generators similar to async functions:

function loadStory() {
  return spawn(function *() {
    try {
      let story = yield getJSON('story.json');
      addHtmlToPage(story.heading);
      for (let chapter of story.chapterURLs.map(getJSON)) {
        addHtmlToPage((yield chapter).html));
      }
      addTextToPage("All done");
    } catch (err) {
      addTextToPage("Argh, broken: " + err.message);
    }
    document.querySelector('.spinner').style.display = 'none';
  });
}

In the above, I'm passing a generator function to spawn, you can tell it's a generator because for the asterisk in function *(). The spawn function calls .next() on the generator, receives the promise at the yield call, and waits for it to resolve before calling .next() with the result (or .throw() if it rejects).

ES7 brings the spawn function into the spec and makes it even easier to use. Having a standard way to simplify async coding like this is fantastic!

Further reading

comments powered by Disqus