Async JavaScript
JavaScript is a single-threaded language which means it has a single call stack. This means that JavaScript in the browser can only do one thing at at time.
As a result, if we have a stack of operations to perform and one of them is really slow, everything else has to wait until the slow operation completes. This is called blocking.
So when we get a network request, we have two options:
1. Do the network request right away, and block everything else.
2. Queue the network request, and run it when we have some spare time.
This used to be done with callbacks
fetchFromSomeApi(function(data) {
// now we have the data we need
})
but say you had to make 3 requests:
fetchFromSomeApi(function(data1) {
fetchFromSomeOtherApi(function(data2) {
fetchFromSomeOtherOtherApi(function(data3) {
// well this is confusing.
// and what if one of these goes wrong?
})
})
})
So we moved on, and in ES2015, we gained Promises.
Promises can resolve or reject.
A resolved promise can be said to have fulfilled.
A rejected promise is said to have been rejected.
A promise can also be pending, where we don't know the state. For example, we made a network request but haven't had a response back.
Promise.resolve can create a promise that resolves with a value.
Promise chains.
These can be thought of like a pipeline.
this is how we give a function that will be called with the value inside the box.
promise functions can return the next value
Promise.resolve(5)
.then(value => {
return value + 1;
}).then(value => {
console.log(value)
})
Let's play with promises.
npm run exercise async 1
open the console!
Wrapping callbacks with promises
setTimeout(() => {
console.log('I will run after 5 seconds!')
}, 5000)
if a function uses callbacks, we can wrap it in a promise
new Promise()
const timeoutPromise = new Promise(function(resolve) {
setTimeout(() => resolve(), 5000)
})
timeoutPromise.then(() => {
console.log('I will run after 5 seconds')
})
when we create a new promise, we get a function that we can call when we want the promise to resolve
const timeoutPromise = new Promise(function(resolve) {
setTimeout(() => resolve(), 5000)
})
timeoutPromise.then(() => {
console.log('I will run after 5 seconds')
})
npm run exercise async 2
when promises go wrong
Promise.resolve(5).then(value => {
throw new Error('Jack made this promise go wrong on purpose')
})
"uncaught"
Promise.resolve(5).then(value => {
throw new Error('Jack made this promise go wrong on purpose')
}).catch(e => {
console.log('We caught the error!')
})
npm run exercise async 3
error handling
.then/.catch in promise chains
we catch the error and deal with it
and return a new value
does this get run?
promise chains with error handling
npm run exercise async 4
promises returning promises
you can never have nested promises.
npm run exercise async 5
the fetch API
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => {
return response.json()
})
.then(todo => {
console.log('got todo')
})
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => {
return response.json()
})
.then(todo => {
console.log('got todo')
})
returns a promise that resolves with the response, parsed to JSON.
wrapping the fetch API promise in a function
npm run exercise async 6
sequential promises
fetch a photo, and then fetch its album
npm run exercise async 7
parallel requests
fetch recent photos and recent albums
Promise.all
npm run exercise async 8
Promise.all([
Promise.resolve(5),
Promise.resolve(6)
]).then(values => {
console.log(values) // [5, 6]
})
async / await
Part of ES2017
async / await is syntactic sugar over promises
when you write async/await, you are using promises
fetch('/posts').then(response => response.json()).then(posts => {...})
const posts = await fetch('/posts').then(response => response.json())
you can only use await in a function that's marked with async
the async keyword denotes a function that returns a promise
const fetchPhoto = async id => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/photos/${id}`
)
return response.json()
}
we don't need await here because the return value is automatically wrapped in a promise, because our function is async
npm run exercise async 9
const fetchPhoto = async id => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/photos/${id}`
)
return response.json()
}
Promise.resolve(5)
.then(value => value + 1)
.then(value => value + 2)
.then(value => value + 3)
.then(value => {
logPromiseValue(1, value)
})
const asyncVersion = async () => {
const firstValue = await Promise.resolve(5)
logPromiseValue(2, firstValue + 1 + 2 + 3)
}
async/await can simplify promise chains
simplify the promise chain using async/await
npm run exercise async 10
straight into another one! fetch a photo and its album using async await
npm run exercise async 11
error handling with async await
we simply use try/catch
try {
const response = await fetch('/photos')
} catch (e) {
// got an error!
}
try {
const response = await fetch('/photos')
const nextThing1 = await fetch(...)
const nextThing2 = await fetch(...)
const nextThing3 = await fetch(...)
const nextThing4 = await fetch(...)
} catch (e) {
// this will catch all errors from above.
}
catch the error!
npm run exercise async 12
async/await is sequential!
const photos = await fetchAllPhotos()
const albums = await fetchAllAlbums()
these are not run in parallel!
we will wait for photos before fetching albums
because async/await uses promises, we can use async with promises
const photosAndAlbums = await Promise.all([
fetchPhotos(),
fetchAlbums()
])
this will run in parallel as we expect
fix up the slowness
npm run exercise async 13
a...sync we are done here!
setting up with Babel
https://stackoverflow.com/a/28709165
TLDR:
babel-plugin-transform-runtime
Async JS
By Jack Franklin
Async JS
- 766