Today we will touch on one of the most advance concept of JavaScript : Asynchronous.
By default JavaScript execute synchronously, meaning from line 1 to last line of JS file, top to botom. This is one limitation if we running this scenario :
You get the idea, JS don't wait! But we could tell it to wait. Kind of
In programming, we often have a "producing code" that does something and takes time. For instance, code that loads the data over a network. We also have "consuming code" that wants the result of the "producing code" once it's ready. A promise is a special JavaScript object that links the "producing code" and the "consuming code" together.
let promise = new Promise(function (resolve, reject) {
// executor
setTimeout(() => resolve("done"), 1000);
});
console.log(promise);
// Promise { <pending> }
The function passed to new Promise
is called the executor. When new Promise
is created, the executor runs automatically. Its arguments resolve
and reject
are callbacks provided by JavaScript itself. Our code is only inside the executor. When the executor obtains the result, be it soon or late, doesn't matter, it should call one of these callbacks:
resolve(value)
: if the job finished successfully, with result value.reject(error)
: if an error occurred, error is the error object.Consumers: then, catch, finally
A Promise object serves as a link between the executor and the consuming functions, which will receive the result or error.
The most important, fundamental one is .then
promise.then(
function (result) {
/* handle a successful result */
},
function (error) {
/* handle an error */
}
);
Example
let promise = new Promise(function (resolve, reject) {
// executor
setTimeout(() => resolve("done"), 1000);
});
// resolve runs the first function in .then
promise.then(
(result) => console.log(result), // "done" after 1 second
(error) => console.log(error) // doesn't run
);
let promise = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error("Whoops!")), 1000);
});
// reject runs the second function in .then
promise.then(
(result) => console.log(result), // doesn't run
(error) => console.log(error) // shows "Error: Whoops!" after 1 second
);
If we're interested only in successful completions, then we can provide only one function argument to .then
:
promise.then(console.log);
If we're interested only in errors, then we can use null
as the first argument: .then(null, errorHandlingFunction)
. Or we can use .catch(errorHandlingFunction)
, which is exactly the same:
let promise = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error("Whoops!")), 1000);
});
promise.catch(console.log); // shows "Error: Whoops!" after 1 second
The call .finally(cb)
means that the function cb
(callback) always runs when the promise is settled (resolve or reject). finally
is good handler for performing cleanup.
There's a special syntax to work with promises in a more comfortable fashion, called "async/await". It's surprisingly easy to understand and use.
Async
The word async
before a function means one simple thing: a function always returns a promise. Other values are wrapped in a resolved promise automatically.
async function f() {
return 1;
}
f().then(alert); // 1
Await
The keyword await
makes JavaScript wait until that promise settles and returns its result.
async function f() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("done!"), 1000);
});
let result = await promise; // wait until the promise resolves (*)
console.log(result); // "done!"
}
f();
The function execution "pauses" at the line (*) and resumes when the promise settles, with result becoming its result. So the code above shows "done!" in one second.
Let's emphasize: await literally suspends the function execution until the promise settles, and then resumes it with the promise result. That doesn't cost any CPU resources, because the JavaScript engine can do other jobs in the meantime: execute other scripts, handle events, etc.
It's just a more elegant syntax of getting the promise result than promise.then, easier to read and write.
Note: you can't use await
in non-async function, there would be a syntax error
Note: you can't use await
in top-level code, you need to wrap it into an async
function
In the case of a rejection, await promise
throws an error. We can catch that error using try..catch
:
async function f() {
try {
let response = await fetch("/no-user-here");
let user = await response.json();
} catch (err) {
// catches errors both in fetch and response.json
alert(err);
}
}
f();
The async
keyword before a function has two effects:
await
to be used in it.The await
keyword before a promise makes JavaScript wait until that promise settles, and then:
throw error
were called at that very place.Together they provide a great framework to write asynchronous code that is easy to both read and write.
With async/await
we rarely need to writepromise.then/catch
, but we still shouldn't forget that they are based on promises, because sometimes (e.g. in the outermost scope) we have to use these methods. Also Promise.all
is nice when we are waiting for many tasks simultaneously.
Today we'll clone Google News. We'll use the News API to do so.
This app will show the latest news to the user. This should be a pretty exciting, as it's your first "real" app that could be useful to an end user. Congratulations on making it this far.
mkdir 3.3-codernews
cd 3.3-codernews
touch index.html style.css script.js
JavaScript can send network requests to the server and load new information whenever it's needed.
There's an umbrella term "AJAX" (abbreviated Asynchronous JavaScript And XML) for network requests from JavaScript. We don't have to use XML though: the term comes from old times, that's why that word is there. You may have heard that term already.
The fetch()
method is a way to send a network request and get information from the server.
let promise = fetch(url, [options]);
options
is optional parameters: method, headers, etc.. Without options
, that is a simple GET
request, downloading the contents of the url
.
fetch()
return a promise
which resolves with an object of the built-in Response class as soon as the server responds with headers.
To the the response body, we need to use an additional method call
let response = await fetch(url);
let commits = await response.json(); // read response body and parse as JSON
Or the same without await
, using pure promises syntax:
fetch(url')
.then(response => response.json())
.then(commits => alert(commits[0].author.login));
POST requests
let user = {
name: "John",
email: "john2020@gmail.com",
};
let response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json;charset=utf-8",
},
body: JSON.stringify(user),
});
let result = await response.json();
console.log(result.message);
Fetch options:
method
– HTTP-methodheaders
– an object with request headersbody
– the data to send (request body)We always want new content, whether the user comes today or next year. We'll do so via API.
script.js
to make sure we've properly loaded it.url
which is the endpoint we'll fetch()
from.Make sure to replace the parameter apiKey
with the one we got a moment ago below.
const url =
"https://newsapi.org/v2/top-headlines?country=us&apiKey=___OUR_API_KEY___";
getArticles()
, which is asynchronously fetches the news from the API and then parses the response.Log the data you get back and make sure it's what we expect.
async function getArticles() {
const response = await fetch(url);
const json = await response.json();
console.log({ json });
}
getArticles()
function and look at the json you've gotten back from the API in the console.getArticles();
Negative : If you encounter CORS issues try navigating to http://localhost:YOUR_PORT
instead of the default IP opened by live server. http://localhost:5500/
instead of http://127.0.0.1:5500/
Alright we've gotten the data, now we need to inject it to our UI.
async function getArticles() {
const response = await fetch(url);
const json = await response.json();
const { articles } = json;
document.getElementById("title").innerHTML = `CoderNews (${articles.length})`;
}
Here we pass a callback to map()
, renderArticle()
. This callback will produce the HTML for a singleArticle
async function getArticles() {
const response = await fetch(url);
const json = await response.json();
const { articles } = json;
document.getElementById("title").innerHTML = `CoderNews (${articles.length})`;
const articlesHTML = articles.map(renderArticle);
document.getElementById("newsList").innerHTML = articlesHTML.join("");
}
renderArticle()
.function renderArticle(article) {
return `
<li class="mb-3 align-self-center article">
${article.title}
<img src="${article.urlToImage}" alt="Snow" />
</div>
<i class="fa fa-edit fa-xs"></i><h4 class="mb-0">${article.author}</h4>
<h6 class="mb-0"><a href="${article.url}">${article.source.name}</a></h6>
<p><i class="fa fa-envelope"></i>${article.content}</p>
</li>
`;
}
renderArticle()
to make the news article more beautiful.Positive : If you encounter CORS issues you can try this link's suggestions.
Grade | Description |
F | Some requirements done and code on Github. |
C | All requirements done and code on Github. |
A | All requirements done and code on Github. App beautiful and works well on mobile |