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
- https://www.pluralsight.com/guides/introduction-to-asynchronous-javascript
- https://developers.google.com/web/fundamentals/primers/promises
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
- https://blog.lavrton.com/javascript-loops-how-to-handle-async-await-6252dd3c795
- https://hackernoon.com/async-await-essentials-for-production-loops-control-flows-limits-23eb40f171bd
- https://caniuse.com/β
- https://bundlephobia.com/β
- https://commons.wikimedia.org/β
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.
- 991