π§πΎβπ» prep
π«±πΏβπ«²π½ Promises
Learning Objectives
graph LR Asynchrony --> |delivered with| Promise{{π€ Promises}} Promise --> |resolve to a| Response{π€ Response} Promise --> |join the| EventLoop{{Event Loop π}}
To get data from a server, we make a request with fetch
. We act on what comes back: the response. But what happens in the middle? We already know that JavaScript is single-threaded: it can only do one thing at a time.
So do we just stop and wait? No! We have a special object to handle this time problem. Run this code in your Node REPL:
const url = "https://api.github.com/users/SallyMcGrath"; // try your own username
const response = fetch(url);
console.log(response);
Your Promise should look like this:
Promise {
Response {
[Symbol(realm)]: null,
[Symbol(state)]: {
aborted: false,
rangeRequested: false,
timingAllowPassed: true,
requestIncludesCredentials: true,
type: 'default',
status: 200,
timingInfo: [Object],
cacheState: '',
statusText: 'OK',
headersList: [HeadersList],
urlList: [Array],
body: [Object]
},
[Symbol(headers)]: HeadersList {
cookies: null,
[Symbol(headers map)]: [Map],
[Symbol(headers map sorted)]: null
}
},
[Symbol(async_id_symbol)]: 54,
[Symbol(trigger_async_id_symbol)]: 30
}
The response
in this code is not labelling the data. It’s labelling a Promise
.
A promise is exactly what it sounds like: a promise to do something. You can use this promise object to sequence your code. You can say, “When the data comes back, then
do this.”
You will explore Promises in more detail as you build more complex applications. For now, let’s move on to .then()
.
πͺ .then()
Learning Objectives
graph LR Promise{{π€ Promises}} --> |resolve to a| Response{π€ Response} Response ---> |sequence with| then{{πͺοΈ then}}
.then()
is a method that belongs to the Promise
then
is a method available on any Promise.
- given a request to
fetch
some data - when the
response
comes back / the promise resolves to a response object then
do this next thing with the data / execute this callback
The .then()
method takes in a callback function that will run once the promise resolves.
For example:
const url = "https://api.github.com/users/SallyMcGrath";
const callback = (response) => response.json(); // .json() is an instance method that exists for all Response objects.
fetch(url).then(callback);
We can also inline the callback
variable here - this code does exactly the same as the code above:
const url = "https://api.github.com/users/SallyMcGrath";
fetch(url).then((response) => response.json());
It’s a similar idea as the event loop we have already investigated, but this time we can control it clearly. The .then()
method queues up callback functions to execute in sequence once the asynchronous operation completes successfully. This allows us to write code as if it was happening in time order.
Note
then()
method of a Promise
always returns a new Promise
.We can chain multiple .then()
calls to run more logic, passing the resolved value to the next callback in the chain. This allows us to handle the asynchronous response in distinct steps. Let’s create a getProfile function which we can try out in our Node REPL:
const getProfile = (url) => {
return fetch(url)
.then((response) => response.json()) // This callback consumes the response and parses it as JSON into an object.
.then((data) => data.html_url) // This callback takes the object and gets one property of it.
.then((htmlUrl) => console.log(htmlUrl)); // This callback logs that property.
};
getProfile("https://api.github.com/users/SallyMcGrath");
So then
returns a new Promise
, and you can call then
again on the new object. You can chain Promises in ever more complex dependent steps. This is called Promise chaining.
It’s important to understand some of what is happening with Promises and then
. But for the most part, you will not be writing code in this style.
π¬ async/await
Learning Objectives
graph LR Promise{{π€ Promises}} --> |syntax| async{{πββοΈ async}} async --> |syntax| await{{π await}} await --> |resolves to| Response{{π€ Response}}
Async/await is
await
inside an async
function or at the top level of a module.
We use the async
keyword to define a function that returns a Promise. An async function always returns a Promise.
We can see this with a simple function which doesn’t need to await anything:
const getProfile = async (url) => url;
console.log(getProfile("hello")); // Logs a Promise.
getProfile("hello").then((value) => console.log(value)); // Logs a value
Even though the function above doesn’t have a time problem, the fact that we define the function as an async
function means it returns a Promise
.
But let’s do something more interesting - let’s actually solve a time problem.
const getProfile = async (url) => {
// the async keyword tells us this function handles a time problem
};
We use the await
operator to wait for a Promise to resolve. This allows us to write code that looks like it’s happening in time order, but doesn’t block our main thread.
const getProfile = async (url) => {
const response = await fetch(url);
return response.json();
};
Go ahead and call this in your Node REPL in your terminal: getProfile("https://api.github.com/users/SallyMcGrath").then(console.log)
. It works the same as before.
π« Handling errors
When we use await
, we are saying, “Wait for this Promise to resolve before moving on to the next line of code.” But if the Promise doesn’t resolve, the next line of code will never run and an error will be thrown.
Let’s try this. Call getProfile
with a url that doesn’t exist: getProfile("invalid_url");
You will get a curious response:
Uncaught (in promise) TypeError...
getProfile("invalid_url")
Promise {
<pending>,
[...]
}
> Uncaught [TypeError: Failed to parse URL from invalid_url] {
[cause]: TypeError: Invalid URL
[...] {
code: 'ERR_INVALID_URL',
input: 'invalid_url'
}
}
Some lines redacted […] for clarity.
JavaScript is telling us we need to catch
the error, but how, and why?
π₯ try/catch
Learning Objectives
We can handle errors with a try/catch block. We can use the try
keyword to try to do something, and if it fails, catch
the
throw
keyword.
const getProfile = async (url) => {
try {
const response = await fetch(url);
return response.json();
} catch (error) {
console.error(error);
}
};
Let’s trigger an error to see this in action. In a Node REPL in your terminal, call getProfile on an API that does not exist again:
getProfile("invalid_url");
TypeError: Failed to parse URL from invalid_url
[...]
[cause]: TypeError: Invalid URL
[...]
code: 'ERR_INVALID_URL',
input: 'invalid_url'
It’s actually the same error you saw before, without the word ‘Uncaught’ before it. But why do we care about this? It’s not obvious in this simple, single function. If we don’t catch the error, the function will
You need to tell JavaScript what to do when something goes wrong, or it will give up completely. In fact, in synchronous programming, the entire program would crash. In asynchronous programming, only the function that threw the error will crash. The rest of the program will continue to run.
Tip
π ποΈ fetch films
Learning Objectives
Now that we have a basic understanding of Web APIs and Promises, let’s use our knowledge to get some data from an API. There’s a list of films stored in a JSON file in this directory. We’ll use fetch
to get the data from this API and then render it to the page.
π― Success criterion: You have a working app that fetches data from an API and renders it to the page.
π§ Think back to your filterFilms project.
- Find your completed code. You’re going to iterate on this code to fetch the films from the API instead of using the data in the file.
- Update the state to start with an empty array. We can’t work with films we haven’t fetched yet!
const state = {
films: [],
};
Make a new
getFilms
function to usefetch
to get the data from the API. The URL is//curriculum.codeyourfuture.io/js3/blocks/fetch-films/data.json
Use:
fetch
to get the dataasync
/await
to make sure the function waits for the fetch to complete before trying to get the json data from the responseresponse.json()
to get the data from the response- a
try...catch
block to handle any errors that might occur
const getFilms = async () => {
try {
const response = await fetch(
"//curriculum.codeyourfuture.io/js3/blocks/fetch-films/data.json"
);
return await response.json();
} catch (error) {
console.error(error);
return [];
}
};
We’ve added a try...catch
block to handle any errors that might occur. We’ve also added await
to the fetch
and response.json()
calls. This means that the function will sensibly wait for the fetch
to complete before trying to get the json data from the response.
In our last implementation, we called the render function straight away. This time, we need to wait for the films to be fetched before we can render them. Write a new async function to initialise our app. Try to write it yourself first, then check your understanding below.
Your init
function should look something like this:
init
function should look something like this:// Initial render, which is distinct from the render function as it loads our films into memory from the API.
// Subsequent render calls do not need to call the API to get the films - we already know the films and can remember them.
async function init() {
try {
const films = await getFilms();
state.films = films;
render(filmContainer, films);
} catch (error) {
console.error(error);
}
}
The name init
is a convention. It has no special meaning in the JavaScript language.
π Finally!
And let’s now call this function at the end of our script.
init();
Need help?
Teamwork Project Presentation prep π
Learning Objectives
Preparation
Introduction
Being part f a product team is about teamwork and delivering working software that gives value to the user. An important aspect is selling your product, the same way you market yourself when searching for a job.
Work in a team on how all of you will present your product.
Creating the presentation
π― Goal: Prepare a presentation for a product pitch as a team (120 minutes)
You will create a 5-minute long presentation about:
- Your productβs brief (what is your MVP, who are your users, what is the value, etc.)
- How have you worked together so far
- How have you managed conflicts within your group
- What would your barriers be if you were to build this product in real life
- How would you overcome those barriers to successfully launch your product
Share your presentation (e.g. PowerPoint slides, Word doc, hand-made poster, etc.). on your project board.
Practice presenting
π― Goal: To practice presenting your product (60 minutes)
Presentation format:
- Each team will have 5 minutes to present
- Everyone in your group should get a chance to speak; some of you may talk longer and some shorter
Identify which content/slides will be delivered by whom in the team.
Practice present, give each other feedback, review it and practice again:
- is the presenter’s communication clear
- is the presenterβs body language aligned with what they are saying
- can the team keep time to 5 minutes (this includes the time to get the presentation up and running)