lec-js-10
April 5th: Project Submission
April 11th: Final Exam
March 27th: hoedown in B107
I'll be staying there until 8 or 9.
00a-recall
console.log("start");
const endpoint = // some endpoint that returns JSON
fetch(endpoint)
.then((resp) => resp.json())
.then(() => console.log("well THAT's interesting"));
console.log("done");🤔 What is logged to the console?
00b-recall
function doThisFirst(data) {
results.push("doThisFirst");
console.log(results);
return {
count: data.length,
data: data.map((d) => d.age),
};
}
function doThisNext(data) {
results.push("doThisNext");
results.push(data.count);
console.log(results);
}
function doThisLast(data) {
results.push("doThisLast");
results.push(data);
console.log(results);
}
let someUrl = ""; // something
const results = [];
fetch(someUrl)
.then((response) => response.json())
.then(doThisFirst)
.then(doThisNext)
.then(doThisLast);🤔 What is logged to the console?
00c-recall
function logAirport(airportApiResponse) {
let airportPromise = airportApiResponse.json();
console.log("immediately after json()", airportPromise);
airportPromise.then(logIt);
}
function logIt(something) {
console.log("logIt callback called with:", something);
}
let airportApiResponsePromise = fetch(Endpoint.FAST);
console.log("immediately after fetch():", airportApiResponsePromise);
airportApiResponsePromise.then(logAirport);
console.log("just before stack is empty:", airportApiResponsePromise);🤔 What is logged to the console? If a Promise is being logged, put a
{ P [[state]], [[result]] } with state either being "fulfilled" or ''pending" and result being the result when the Promise changes from pending to fulfilled.
00d-recall
console.log("start");
let firstAwait = await fetch(Endpoint.FAST);
let secondAwait = await fetch(Endpoint.SLOW);
let thirdAwait = await fetch(Endpoint.THE_404);
firstAwait.json().then(console.log);
secondAwait.json().then(console.log);
thirdAwait.json().then(console.log);
console.log("logging firstFetch", firstAwait);
console.log("logging secondFetch", secondAwait);
console.log("logging thirdFetch", thirdAwait);
console.log("end");🤔 What is logged to the console?
console.log("start");
let firstAwait = await fetch(Endpoint.FAST);
console.log("first done");
let secondAwait = await fetch(Endpoint.SLOW);
console.log("second done");
let thirdAwait = await fetch(Endpoint.SUPERSLOW);
console.log("third done");
console.log("logging firstAwait", firstAwait);
console.log("logging secondAwait", secondAwait);
console.log("logging thirdAwait", thirdAwait);
console.log("end");🤔 What is logged to the console?
🤔 What do you notice about the delays between logs?
🤔 If we switch the speed of the FAST and SUPERSLOW calls, what do you expect to happen?
🤔 If we look in the Network tab of the devtools, what do we see? Does this make sense?
🤔 Based on our observations, how much time is needed to do consecutive awaits?
01-consecutive-awaits
A browser is a powerful beastie, one that can spin up multiple threads to accomplish tasks in parallel.
Surely we can take advantage of this?
Well, yuh.
console.log("start");
let firstAwait = fetch(Endpoint.FAST);
let secondAwait = fetch(Endpoint.SLOW);
let thirdAwait = fetch(Endpoint.SUPERSLOW);
Promise.all([firstAwait, secondAwait, thirdAwait]);
console.log("logging firstAwait", firstAwait);
console.log("logging secondAwait", secondAwait);
console.log("logging thirdAwait", thirdAwait);
console.log("end");🤔 What is logged to the console?
🤔 What do you notice about the delays between logs?
🤔 If we switch the speed of the FAST and SUPERSLOW calls, what do you expect to happen?
🤔 If we look in the Network tab of the devtools, what do we see? Does this make sense?
🤔 Based on our observations, how much time is needed when you use Promise.all?
02-promise-all
console.log("start");
let firstAwait = fetch(Endpoint.FAST);
let secondAwait = fetch(Endpoint.SLOW);
let thirdAwait = fetch(Endpoint.SUPERSLOW);
Promise.all([firstAwait, secondAwait, thirdAwait]);
console.log("logging firstAwait", firstAwait);
console.log("logging secondAwait", secondAwait);
console.log("logging thirdAwait", thirdAwait);
console.log("end");🤔 What is logged to the console?
🤔 What do you notice about the delays between logs?
🤔 If we switch the speed of the FAST and SUPERSLOW calls, what do you expect to happen?
🤔 If we look in the Network tab of the devtools, what do we see? Does this make sense?
🤔 Based on our observations, how much time is needed when you use Promise.all?
02a-promise-all-exp-1
console.log("start");
let firstAwait = fetch(Endpoint.FAST);
let secondAwait = fetch(Endpoint.SLOW);
let thirdAwait = fetch(Endpoint.SUPERSLOW);
Promise.all([firstAwait, secondAwait, thirdAwait]);
console.log("logging firstAwait", firstAwait);
console.log("logging secondAwait", secondAwait);
console.log("logging thirdAwait", thirdAwait);
console.log("end");🤔 What is logged to the console?
🤔 What do you notice about the delays between logs?
🤔 If we switch the speed of the FAST and SUPERSLOW calls, what do you expect to happen?
🤔 If we look in the Network tab of the devtools, what do we see? Does this make sense?
🤔 Based on our observations, how much time is needed when you use Promise.all?
Once three letters are entered in the Airport Code box, the airport API endpoint is queried, and the resulting city is shown.
01-promise-chain
CommonElems.codeEntry.addEventListener("input", handleCodeEntry);
function handleCodeEntry(inputEvent) {
let currentCode = inputEvent.target.value;
inputEvent.target.value = currentCode.toUpperCase();
if (currentCode.length === 3) {
displayCityWhenAvailable(currentCode);
} else {
CommonElems.city.textContent = "";
}
}Wishful coding at work.
I like naming things involving Promises "doBlahBlahWhenAvailable" because it captures the idea that I'm not in control of when something I need is available! I'm just saying "could you please do some things for me when the stuff I need is ready?"
function displayCityWhenAvailable(code) {
let uri = Endpoint.for(code);
apiResponseFromFetch(uri)
.then(airportFromApiResponse)
.then(cityFromAirport)
.then(displayCity);
}Once we have ONE function that runs asynchronously (hi, fetch()!), then every piece of code that relies on the value that the original function returns must also be asynchronous.
Each then() returns a Promise that will EVENTUALLY have a [[PromiseResult]] - the return value of the callback!
function displayCityWhenAvailable(code) {
let uri = Endpoint.for(code);
apiResponseFromFetch(uri)
.then(airportFromApiResponse)
.then(cityFromAirport)
.then(displayCity);
}function airportFromApiResponse(airportApiResponse) {
let airportPromise = airportApiResponse.json();
return airportPromise;
}function cityFromAirport(airport) {
return airport.city;
}function displayCity(city) {
CommonElems.city.textContent = city;
}The last then() in this kind of recipe will use a callback that populates the DOM somehow. This callback doesn't need to return anything, since we don't need any further then()s!
function apiResponseFromFetch(uri) {
return fetch(uri);
}We could start the chain with fetch()...but I personally do this because I can name things more clearly for my aging brain.
I can technically omit this, since our displayCity method could just access airport.city itself. Still, if the API object was complex (hi, Project!), the technique illustrated here can be very useful.
There's 2 tricky things happening here! First off, what does json() return? For the second, see the Return value entry for then() in the MDN docs.
I prefer to use function declarations in my chains, because then I can be very explicit about what is flowing from then() to then().
function displayCityWhenAvailable(code) {
let uri = Endpoint.for(code);
fetch(uri)
.then((airportApiResponse) => airportApiResponse.json())
.then((airport) => airport.city)
.then(displayCity);
}Sure...but ask yourself, "Do I really understand what's flowing between the then()s"?
If you do decide to go this route, at the very least, name your parameters expressively!
Assume that we want to display a message if an unknown/invalid code is entered.
function airportFromApiResponse(airportApiResponse) {
// This could also be airportApiResponse.status != "200"
if (!airportApiResponse.ok) {
throw new Error("Response doesn't contain a valid airport.");
}
let airportPromise = airportApiResponse.json();
return airportPromise;
}function displayCityWhenAvailable(code) {
let uri = Endpoint.for(code);
apiResponseFromFetch(uri)
.then(airportFromApiResponse)
.then(cityFromAirport)
.catch(errorMsgFromBadApiResponse)
.then(displayCity);
}function errorMsgFromBadApiResponse(apiErrorMsg) {
console.log(apiErrorMsg);
return "No city found.";
}CHANGE 1
Throw an error when the Response says it has one.
CHANGE 2
Log the error message (optional), and pass on the value we want to display in the DOM.
CHANGE 3
Add a catch() into the chain.
02-promise-chain-with-errors
function airportFromApiResponse(airportApiResponse) {
// This could also be airportApiResponse.status != "200"
if (!airportApiResponse.ok) {
throw new Error("Response doesn't contain a valid airport.");
}
let airportPromise = airportApiResponse.json();
return airportPromise;
}CHANGE 1
Throw an error when the Response says it has one.
function errorMsgFromBadApiResponse(apiErrorMsg) {
console.log(apiErrorMsg);
return "No city found.";
}CHANGE 2
Log the error message (optional), and pass on the value we want to display in the DOM.
function displayCityWhenAvailable(code) {
let uri = Endpoint.for(code);
apiResponseFromFetch(uri)
.then(airportFromApiResponse)
.then(cityFromAirport)
.catch(errorMsgFromBadApiResponse)
.then(displayCity);
}CHANGE 3
Add a catch() into the chain.
🤔Could we move the catch() right after the first then()?
The await keyword "unwraps" a Promise so that you can access the [[PromiseResult]] - when it's available, of course.
You're allowed to use await only if it's inside a function marked async, or if it's outside a function and in a script loaded via type="module".
let response = await fetch(Endpoint.for("yyc"));
console.log(response);This code...
...produces this in the console.
You may feel some some elation at this point.
And may be wondering if there's a catch. 😏
Wait until the Promise is fulfilled, then return the result.
async function responseFromApi(code) {
let response = await fetch(Endpoint.for(code));
return response;
}
console.log(responseFromApi("yyc"));You might try to do something like this...
...which produces this in the console.
That prior elation may be gone now.
🤔Why is this happening? How do we fix it?
04-promise-chain-to-await
async function displayCityWhenAvailable(code) {
let uri = Endpoint.for(code);
let apiResponse = await apiResponseFromFetch(uri);
let airport = await airportFromApiResponse(apiResponse);
let city = await cityFromAirport(airport);
displayCity(city);
}This feels a lot like our usual kind of synchronous coding, right?
This is great, in a way, because it puts us back in our comfort zone...which is nice and cozy, right?
// Log the airport represented by the JSON inside
// an aiportApiResponse.
function logAirport(airportApiResponse) {
let airportPromise = airportApiResponse.json();
console.log("immediately after json()", airportPromise);
airportPromise.then(log);
}
// Console.log something with a message.
function log(whatever) {
console.log("log callback now logs", whatever);
}
let airportApiResponsePromise = fetch(Endpoint.FAST);
console.log("immediately after fetch()", airportApiResponsePromise);
airportApiResponsePromise.then(logAirport);
console.log("just before stack is empty", airportApiResponsePromise);The callbacks you pass to then() have an important effect on the behaviour of further then()s chaining off of it.
The return value of the callback becomes the result passed into the next then() callback.
This allows you to "flow" information from an initial Promise (in our case, the one generated by fetch()), through multiple refinements until it reaches the final then() callback.
displayCityairportPromiseresponsePromise.then()
.then()