Async/Await: The Ajax Toolkit

Async Javascript Is HarD

Async/AwaitΒ 

Basic usage

Basic use

// ...

const res = await fetch(endpointUrl);
const data = await res.json();

// ...do whatever with the data...

Using Promises:

// ...

fetch(endpointUrl)
    .then(res => res.json())
    .then(data => {
        // ...do whatever with the data...
    })

(Await)

In Practice

async () => {

    const res = await fetch(endpointUrl);
    const data = await res.json();
    // ...do whatever with the data...
}

(Async)

Must have this

In Arrow Function:

async function() {

    const res = await fetch(endpointUrl);
    const data = await res.json();
    // ...do whatever with the data...
}

In vanilla.js Function:

In Practice

async () => {
    try {
        const res = await fetch(endpointUrl);
        if (res.ok) {
            const data = await res.json();
            console.log("Final result:", data);
        } else {
            throw Error(res.statusText);
        }
    } catch (err) {
        console.error("Fetch failed:", err);
    }
}

(Try/Catch)

Reads just like normal code (no .catch())

Calling Async Functions

An async function returns a promise automatically

async () => {
    try {
        const res = await myAsyncFunc();
        // do stuff...
    } catch (e) {
        // act on error...
    }
}

Or

myAsyncFunc()
    .then(res => // do stuff...)
    .catch(err => // act on error...)

Calling Async Functions

`await` can only be used in an `async` context. Don't do this:

const aNonAsyncFunc = () => {
    try {
        const res = await myAsyncFunc();
        // do stuff...
    } catch (e) {
        // act on error...
    }
}

Syntax Error

Less Basic Usage

Use Async Anywhere

class MyComp extends React.Component {
  state = { dataItems: [] };

  componentDidMount() {
    (async () => {
      const data = await fetch("endpoint.url");
      const dataItems = await data.json();
      this.setState({ dataItems });
    })();
  }

  render() {
    const { dataItems } = this.state;
    return dataItems.length > 0 ? (
      <ul>{dataItems.map(item => <li>{item.name}</li>)}</ul>
    ) : (
      "Loading..."
    );
  }
}

{

IFE

In a front-end application:

Use Async Anywhere

const main = async () => {

  // Your main process code here

};

main();

In the global scope:

Mechanics

Main Thread

asyncFunc()
const asyncFunc = async () => {

    const resA = await fetch(urlA);
    const dataA = await resA.json();
    // ...do stuff

    const resB = await fetch(urlB);
    const dataB = await resB.json();
    // ...do more stuff

    return [...dataA, ...dataB];

}

Function Execution

Halt

Halt

.then() or await

always running, never blocked

Halt

Halt

A Comparison

const response = await fetch("https://api...");
const json = await response.json();
console.log(json);
fetch("https://api...")
  .then(response => {
    return response.json();
  })
  .then(json => {
    console.log(json);
  });

Async/Await

Promises

Concurrent Async/Await

const get = async (url) => {
    const response = await fetch(url);
    return response.json();
}

const getDatas = async () => {

  // Fire off both requests at the same time
  const dataA = get("endpoint/a"); 
  const dataB = get("endpoint/b");

  // Return once both have resolved
  return { dataA: await dataA, dataB: await dataB};
}

Looping

Looping

const names = [];

fetches.forEach(async (fetchInProcess) => {
    const res = await fetchInProcess;
    const data = await res.json();
    console.log(data.name);
    names.push(data.name);
  });
  
console.log("==FINISHED==");
console.log(names);

// ** Evaluates to: **
//
// "==FINISHED=="
// []
// "Darth Vader"
// "Leia Organa"
// "R2-D2"
// "C-3PO"
// "Luke Skywalker"

Solution?

Use regular for loops

Main Thread

= async actions

(always running, never blocked)

A sequence of async actions can be executed one at a time, in order, waiting to start the next until the previous is finished

Series Loop

Looping

Looping

async (items) => {
  for (item of items) {
    const result = await db.get(items[i]);
    console.log(result);
  }
}

Series Loop

Looping

async (items) => {
  const promises = items.map(
    item => db.get(item)
  );
  
  const results = await Promise.all(promises);
  console.log(results);
}

Parallel Loop

Looping

Main Thread

= async actions

(always running, never blocked)

1

2

3

4

1

2

3

4

{

Execute all actions at once

Parallel Series Loop

Looping

async (items) => {
  // Kick off the async actions
  const promises = items.map(
    item => db.get(item)
  );
  
  for (promise of promises) {
    const data = await promise;
    console.log(data);
  }
}

Parallel Series Loop

Looping Using PRomises

Use Promises In Blocking Sequence

The Flow:

Main Thread

= async actions

(always running, never blocked)

A sequence of async actions can be executed one at a time, in order, waiting to start the next until the previous is finished

Use Promises In Blocking Sequence

const story = {
  title: "The Book of Mormon",
  chapterUrls: ["my.endpoint/1", "my.endpoint/2", "my.endpoint/3"]
};

// Start off with a promise that always resolves
let sequence = Promise.resolve();

// Loop through our chapter urls
story.chapterUrls.forEach((chapterUrl) => {
  // Add these actions to the end of the sequence
  sequence = sequence
    .then(() => getJSON(chapterUrl))
    .then(chapter => addHtmlToPage(chapter.html))
})

The Code: (using a for loop)

const story = {
  title: "The Book of Mormon",
  chapterUrls: ["my.endpoint/1", "my.endpoint/2", "my.endpoint/3"]
};

// Loop through our chapter urls

story.chapterUrls.reduce((sequence, chapterUrl) => {
  // Add these actions to the end of the sequence
  return sequence
    .then(() => getJSON(chapterUrl))
    .then(chapter => addHtmlToPage(chapter.html));
}, Promise.resolve());

The Code: (using Array.prototype.reduce())

Use Promises In Blocking Sequence (Simplified)

Use Promises In Shotgun Sequence

The Flow:

Main Thread

= async actions

(always running, never blocked)

1

2

3

4

1

2

3

4

{

Execute all actions at once

const story = {
  title: "The Book of Mormon",
  chapterUrls: ["my.endpoint/1", "my.endpoint/2", "my.endpoint/3"]
};

story.chapterUrls.map(getJSON) // <-- Fire off all the async actions
    .reduce((sequence, chapterPromise) => {
        // Then use reduce to chain the promises together,
        // adding content to the page for each chapter
        return sequence
            .then(() => {
                // Wait for everything in the sequence so far,
                // then wait for this chapter to arrive.
                return chapterPromise;
            })
            .then(chapter => addHtmlToPage(chapter.html));
    }, Promise.resolve());

Use Promises In Shotgun Sequence

Note: This method will be replaced by `for await of`

Getting Async/Await To Work In The Browser

Async/Await Is Still New

  • Only supported in 85% of users' browsers (June 2018)
  • Polyfill is needed

Web Browsers

  • Need Node.js 8+

Build Requirements

babel-plugin-transform-async-to-generator

∈

babel-plugin-transform-runtime
babel-preset-env

πŸ‘πŸ₯‡

OR

npm install --registry="http://icsnpm.ldschurch.org" -g ldsjs


ldsjs react your-sweet-app-name

Use the ICS Stack Team v3.x React starter

async/await is ready out of the box!

Asyncifying Callback-based Libraries

The Code

const someThing = require('some-thing')

someThingWrapper = () =>
  new Promise(resolve => {
    someThing((err, result) => {
      if (err) {
        reject(err)
      }
      resolve(result)
    })
  })

const main = async () => {
  try {
    const result = await someThingWrapper()
    // do stuff with result
  } catch (err) {
    console.error(err)
  }
}

main()

The End πŸŽ‰

Sources

Async/Await & Promises: The Ajax Toolkit

By Evan Peterson

Async/Await & Promises: The Ajax Toolkit

Covers the `async/await` Javascript feature that entered the spec in ES2017. Includes basic usage, use in a more global context, the mechanics of `async/await`, making concurrent `async/await` requests, other looping and concurrency strategies, and build tool requirements for getting `async/await` to work in the browser.

  • 441
Loading comments...

More from Evan Peterson