- Recognize the problems solved by asynchronous code
- Distinguish between asynchronous and synchronous code
- Use
fetch
to communicate with APIs - Understand how to use a Promise in Javascript
- Use
then
to defer function calls until promises resolve
- JSON - JavaScript Object Notation
- AJAX - Async Javascript And XML (JSON)
Javascript is synchronous: it executes statements one at a time. But... sometimes things take a long time (such as making an HTTP Request)!
We don't want these things to block UI rendering, or block users from interacting with our website. If we are waiting for javascript to run some time-consuming function, the page is not responsive. Our users will get upset and leave us 1 Star ๐ reviews. Our bosses will get mad and we will be sad ๐
function sleep(time){
const start = new Date()
while (new Date() - start < time * 1000){}
}
console.log('Starting the sleep function')
sleep(10)
console.log('Wow that sleep function took forever to run. 1 Star ๐')
Instead of waiting for things to happen synchronously, we want to have some code run later so that our UI rendering isn't blocked.
We've already seen some JS functions that help us with this:
- Callback functions as handlers for event listeners
setTimeout
andsetInterval
That code runs asynchronously!
Let's take a look at some examples of synchronous and asynchronous code
Synchronous:
- Most of ruby
sleep
,RestClient.get
Asynchronous:
setTimeout
andsetInterval
- event listeners
fetch
requests (more on that soon)
Promises
are another way of writing asynchronous code.
"The Promise object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value." - mdn article on promises
In other words, they represent the promise of a particular value that we do not yet have.
For example, part of your morning routine might involve making coffee. You could start your coffee maker and sit while it heats up and brews coffee for you. Or you could start the machine knowing that it will eventually give you some coffee. You can let the coffee machine run asynchronously then drink the coffee once that operation is complete.
Promises just represent some data we will have at a later time. This is especially useful when making HTTP requests that take an arbitrary amount of time to complete.
"Asynchronous JavaScript + XML, while not a technology in itself, is a term coined in 2005 by Jesse James Garrett, that describes a 'new' approach to using a number of existing technologies together, including HTML or XHTML, Cascading Style Sheets, JavaScript, The Document Object Model, XML, XSLT, and most importantly the XMLHttpRequest object. When these technologies are combined in the Ajax model, web applications are able to make quick, incremental updates to the user interface without reloading the entire browser page. This makes the application faster and more responsive to user actions. Although X in Ajax stands for XML, JSON is used more than XML nowadays because of its many advantages such as being lighter and a part of JavaScript. Both JSON and XML are used for packaging information in Ajax model." -MDN Article on AJAX
"What AJAX allows you to do is just update parts of the DOM of a HTML webpage instead of having to reload the entire page. AJAX also lets you work asynchronously, meaning your code continues to run while that part of your webpage is trying to reload (compared to synchronously which will block your code from running until that part of your webpage is done reloading)." - MDN Glossary on AJAX
Recall the request/response cycle; we do not know ahead of time how long an HTTP request will take to complete:
Let's see some real-live promises! We're going to use fetch
, which is a function for sending HTTP requests (in the console:)
const url = "https://dog.ceo/api/breeds/image/random"
const promise = fetch(url, { method: 'GET' })
console.log(promise)
In the snippet above, we're sending an HTTP GET
request to our url using fetch
. Note that if we don't specify the HTTP verb (GET
, POST
, PUT/PATCH
, DELETE
) fetch
will default to a GET
request. So this will work the same:
const url = "https://dog.ceo/api/breeds/image/random"
const promise = fetch(url)
console.log(promise)
See that the promise
object doesn't have the response but is instead a box with the response inside. Whenever we're dealing with promises, the data will always be contained within the promise object itself
If only there were some way to send an asynchronous HTTP request using fetch
and then do something once our promise resolved...
Promises include this functionality: by calling the .then()
on the Promise, we can add a handler that runs when the response arrives (once our promise resolves)
function afterResolved() {
console.log('resolved')
}
promise.then(afterResolved)
.then(
is like adding an event handler - it has code to run later. But we can also still see the promise itself - we have the object!
function otherFunction(data) {
console.log("here's the data from your promise:", data)
}
promise.then(otherFunction)
. then()
takes in a callback function, similar to how an event listener works. One difference: .then()
methods return a Promise as well, which means they can be chained together:
fetch(url)
.then(() => console.log('resolved'))
.then(() => console.log('after logging resolved'))
.then(() => console.log('after logging after resolved'))
Common Bug: evaluating a function instead of passing it to then
Q: When will the message be logged?
fetch(url)
.then(console.log("message"))
A: Before the fetch resolves!
The fix is to use an anonymous function (with the function
keyword or an arrow). .then
is expecting a reference to a function that it will invoke once the promise is resolved. Please please please please do not invoke your method directly within your .then
as shown in the example above. Like event listeners, .then
is expecting a function definition not invocation. It will run your function once the promise has resolved.
The corrected code looks like this:
fetch(url)
.then(() => console.log("message"))
If there's an error on the other end of the fetch, you can handle the error with catch
fetch("https://www.google.com")
.then(res => console.log(res))
.catch(error => console.log("Here's the error:", error))
The cool bonus that we get from .catch()
- if there's an error somewhere in our then
handler, we'll catch that too!
Let's take a closer look at the fetch
method.
fetch("https://dog.ceo/api/breeds/list/all")
.then(res => console.log(res))
- We get back a Promise from
fetch
, which we can add handlers to withthen
- The handler that we pass to
then
gets aResponse
object - We can call the
.json
method on theResponse
to get a promise for the parsed json from the response
fetch("https://dog.ceo/api/breeds/list/all")
.then(res => res.json())
.then(data => console.log(data))
If the response is not formatted as JSON, we can use other methods such as .text
to parse the response, depending on the type of data sent back to us.
fetch("https://dog.ceo/api/breeds/list/all")
.then(res => res.text())
.then(text => console.log(text))