This commit is contained in:
Ilya Kantor 2017-04-21 09:34:49 +02:00
parent 7a51c05ded
commit 0576ea79d8
18 changed files with 375 additions and 109 deletions

View file

@ -13,7 +13,7 @@ function checkAge(age) {
if (age > 18) { if (age > 18) {
return true; return true;
} else { } else {
return confirm('Did parents allow you?'); return confirm('Do you have your parents permission to access this page?');
} }
} }
``` ```
@ -24,4 +24,3 @@ Make two variants of `checkAge`:
1. Using a question mark operator `'?'` 1. Using a question mark operator `'?'`
2. Using OR `||` 2. Using OR `||`

View file

@ -1,10 +1,10 @@
# Browser environment, specs # Browser environment, specs
The JavaScript language was initially created for web browsers. But as of now, it evolved and became a language with many uses and platforms. The JavaScript language was initially created for web browsers. Since then, it evolved and became a language with many uses and platforms.
A platform may be either a browser or a web-server or a washing machine or another *host*. Each of them provides platform-specific functionality. The JavaScript standard called that a *host environment*. A platform may be either a browser or a web-server or a washing machine or another *host*. Each of them provides platform-specific functionality. The JavaScript specification calls that a *host environment*.
That host environment provides additional objects and functions to the language core. Web browsers provide means to control web pages. Node.JS provides server-side features. There are other host environments too. A host environment provides platform-specific objects and functions additionally to the language core. Web browsers give means to control web pages. Node.JS provides server-side features, and so on.
[cut] [cut]
@ -12,10 +12,10 @@ Here's a bird-eye view of what we have when JavaScript runs in a web-browser:
![](windowObjects.png) ![](windowObjects.png)
The "root object" called `window` has two roles: There's a "root" object called `window`. It has two roles:
1. First, it is a [global object](info:global-object) for JavaScript code. 1. First, it is a global object for JavaScript code, as described in the chapter <info:global-object>.
2. Second, it represents the "browser window" object, provides methods to control it. 2. Second, it represents the "browser window" and provides methods to control it.
For instance, here we use it as a global object: For instance, here we use it as a global object:
@ -24,18 +24,21 @@ function sayHi() {
alert("Hello"); alert("Hello");
} }
alert(window.sayHi); // global function is a property of window // global functions are accessible as properties of window
alert(window.sayHi);
``` ```
And here we use it as a browser window, to see the window height: And here we use it as a browser window, to see the window height:
```js run ```js run
alert(window.innerHeight); // some number alert(window.innerHeight); // inner window height
``` ```
There are more window-specific methods and properties, we'll cover them later.
## Document Object Model (DOM) ## Document Object Model (DOM)
The `document` object gives access to the page content. We can change or create literally anything. The `document` object gives access to the page content. We can change or create anything on the page using it.
For instance: For instance:
```js run ```js run
@ -46,24 +49,20 @@ document.body.style.background = 'red';
setTimeout(() => document.body.style.background = '', 1000); setTimeout(() => document.body.style.background = '', 1000);
``` ```
Here we used `document.body.style`, but there's much, much more. Properties and methods are described in the specification. Here we used `document.body.style`, but there's much, much more. Properties and methods are described in the specification. By chance, there are two working groups who develop it:
There are two working groups who develop it:
1. [W3C](https://en.wikipedia.org/wiki/World_Wide_Web_Consortium) -- the documentation is at <https://www.w3.org/TR/dom>. 1. [W3C](https://en.wikipedia.org/wiki/World_Wide_Web_Consortium) -- the documentation is at <https://www.w3.org/TR/dom>.
2. [WhatWG](https://en.wikipedia.org/wiki/WHATWG), publishing at <https://dom.spec.whatwg.org>. 2. [WhatWG](https://en.wikipedia.org/wiki/WHATWG), publishing at <https://dom.spec.whatwg.org>.
As it happens, the two groups don't always agree, so we have like 2 sets of standards. But they are in tight contact and eventually things merge. So the documentation that you can find on the given resources is very similar, like 99%. There are very minor differences, but you probably won't notice them. As it happens, the two groups don't always agree, so we have like 2 sets of standards. But they are in the tight contact and eventually things merge. So the documentation that you can find on the given resources is very similar, there's like 99% match. There are very minor differences, you probably won't notice them.
I find <https://dom.spec.whatwg.org> more pleasant to use, and so recommend it. Personally, I find <https://dom.spec.whatwg.org> more pleasant to use.
In the ancient past, once there was no standard at all -- each browser did whatever it wanted. So different browsers had different sets methods and properties for the same thing, and developers had to write different code for each of them. Dark times indeed. In the ancient past, there was no standard at all -- each browser implemented whatever it wanted. So different browsers had different sets methods and properties for the same thing, and developers had to write different code for each of them. Dark, messy times.
Even now we can sometimes meet old code that uses browser-specific properties and works around incompatibilities. But in this tutorial we'll use modern stuff: there's no need to learn old things until you really need those (chances are high you won't). Even now we can sometimes meet old code that uses browser-specific properties and works around incompatibilities. But in this tutorial we'll use modern stuff: there's no need to learn old things until you really need those (chances are high you won't).
Then the DOM standard appeared, in an attempt to bring everyone to an agreement. The first version was DOM Level 1, then it was extended by DOM Level 2, then DOM Level 3, and now it's DOM Level 4. Then the DOM standard appeared, in an attempt to bring everyone to an agreement. The first version was "DOM Level 1", then it was extended by DOM Level 2, then DOM Level 3, and now it's DOM Level 4. People from WhatWG group got tired of version and are calling that just "DOM", without a number. So will do we.
People from WhatWG group got tired of version and are calling that just "DOM", without a number. So will do we.
```smart header="DOM is not only for browsers" ```smart header="DOM is not only for browsers"
The DOM specification explains the structure of a document and provides objects to manipulate it. There are non-browser instruments that use it too. The DOM specification explains the structure of a document and provides objects to manipulate it. There are non-browser instruments that use it too.
@ -72,9 +71,9 @@ For instance, server-side tools that download HTML pages and process them. They
``` ```
```smart header="CSSOM for styling" ```smart header="CSSOM for styling"
CSS styles and stylesheets are structured not like HTML. So there's a separate specification [CSSOM](https://www.w3.org/TR/cssom-1/) that explains how CSS styles and rules can be represented as objects, how to read and write them. CSS rules and stylesheets are structured not like HTML. So there's a separate specification [CSSOM](https://www.w3.org/TR/cssom-1/) that explains how they are represented as objects, how to read and write them.
Usually we take a style somewhere from the document and apply it to the document, so CSSOM is used together with DOM. But CSSOM is applied not often, because we rarely need to modify CSS rules from JavaScript, so we won't cover it right now. CSSOM is used together with DOM when we modify style rules for the document. In practice though, CSSOM is rarely required, because usually CSS rules are static. We rarely need to add/remove CSS rules from JavaScript, so we won't cover it right now.
``` ```
## BOM (part of HTML spec) ## BOM (part of HTML spec)
@ -95,7 +94,7 @@ if (confirm("Go to wikipedia?")) {
} }
``` ```
Functions `alert/confirm/prompt` -- are also a part of BOM: they are directly not related to the document, but represent pure browser methods of communicating with the user. Functions `alert/confirm/prompt` are also a part of BOM: they are directly not related to the document, but represent pure browser methods of communicating with the user.
```smart header="HTML specification" ```smart header="HTML specification"
@ -112,11 +111,13 @@ DOM specification
: Describes the document structure, manipulations and events, see <https://dom.spec.whatwg.org>. : Describes the document structure, manipulations and events, see <https://dom.spec.whatwg.org>.
CSSOM specification CSSOM specification
: Describes styles, manipulations with them and their binding to documents, see <https://www.w3.org/TR/cssom-1/>. : Describes stylesheets and style rules, manipulations with them and their binding to documents, see <https://www.w3.org/TR/cssom-1/>.
HTML specification HTML specification
: Describes HTML language (tags etc) and also BOM (browser object model) -- various browser functions: `setTimeout`, `alert`, `location` and so on, see <https://html.spec.whatwg.org>. It takes DOM specification and extends it with many additional properties and methods. : Describes HTML language (tags etc) and also BOM (browser object model) -- various browser functions: `setTimeout`, `alert`, `location` and so on, see <https://html.spec.whatwg.org>. It takes DOM specification and extends it with many additional properties and methods.
Now we'll get down to learning DOM, because the document plays the central role in the UI, and working with it is the most complex part. Now we'll get down to learning DOM, because the document plays the central role in the UI, and working with it is the most complex part.
Please note the links above, because there's so many stuff to learn, it's impossible to cover and remember everything. When you'd like to read about a property or a method -- the Mozilla manual at <https://developer.mozilla.org/en-US/search> is a nice one, but reading the corresponding spec may be better. More complex and longer to read, but will make your fundamental knowledge sound and complete. Please note the links above, because there's so many stuff to learn, it's impossible to cover and remember everything.
When you'd like to read about a property or a method -- the Mozilla manual at <https://developer.mozilla.org/en-US/search> is a nice one, but reading the corresponding spec may be better: more complex and longer to read, but will make your fundamental knowledge sound and complete.

View file

@ -6,9 +6,13 @@ libs:
# DOM tree # DOM tree
The essential part of HTML is tags, right? The backbone of an HTML document is tags.
According to Document Object Model (DOM), every HTML-tag is an object. Nested tags are called "children". And the text inside it is an object as well. All these objects are accessible using JavaScript, we'll see that now. According to Document Object Model (DOM), every HTML-tag is an object. Nested tags are called "children" of the enclosing one.
The text inside a tag it is an object as well.
All these objects are accessible using JavaScript.
## An example of DOM ## An example of DOM

View file

@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
.circle {
transition-property: width, height, margin-left, margin-top;
transition-duration: 2s;
position: fixed;
transform: translateX(-50%) translateY(-50%);
background-color: red;
border-radius: 50%;
}
</style>
</head>
<body>
<button onclick="showCircle(150, 150, 100)">showCircle(150, 150, 100)</button>
<script>
function showCircle(cx, cy, radius) {
let div = document.createElement('div');
div.style.width = 0;
div.style.height = 0;
div.style.left = cx + 'px';
div.style.top = cy + 'px';
div.className = 'circle';
document.body.append(div);
setTimeout(() => {
div.style.width = radius * 2 + 'px';
div.style.height = radius * 2 + 'px';
}, 0);
}
</script>
</body>
</html>

View file

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
.circle {
transition-property: width, height, margin-left, margin-top;
transition-duration: 2s;
position: fixed;
transform: translateX(-50%) translateY(-50%);
background-color: red;
border-radius: 50%;
width: 200px;
height: 200px;
top: 150px;
left: 150px;
}
</style>
</head>
<body>
<div class="circle"></div>
</body>
</html>

View file

@ -0,0 +1,16 @@
importance: 5
---
# Animated circle
Create a function `showCircle(cx, cy, radius)` that shows an animated growing circle.
- `cx,cy` are window-relative coordinates of the center of the circle,
- `radius` is the radius of the circle.
Click the button below to see how it should look like:
[iframe src="solution" height=260]
The source document has an example of a circle with right styles, so the task is precisely to do the animation right.

View file

@ -0,0 +1,59 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
.message-ball {
font-size: 20px;
line-height: 200px;
text-align: center;
}
.circle {
transition-property: width, height, margin-left, margin-top;
transition-duration: 2s;
position: fixed;
transform: translateX(-50%) translateY(-50%);
background-color: red;
border-radius: 50%;
}
</style>
</head>
<body>
<button onclick="go()">Click me</button>
<script>
function go() {
showCircle(150, 150, 100, div => {
div.classList.add('message-ball');
div.append("Hello, world!");
});
}
function showCircle(cx, cy, radius, callback) {
let div = document.createElement('div');
div.style.width = 0;
div.style.height = 0;
div.style.left = cx + 'px';
div.style.top = cy + 'px';
div.className = 'circle';
document.body.append(div);
setTimeout(() => {
div.style.width = radius * 2 + 'px';
div.style.height = radius * 2 + 'px';
div.addEventListener('transitionend', function handler() {
div.removeEventListener('transitionend', handler);
callback(div);
});
}, 0);
}
</script>
</body>
</html>

View file

@ -0,0 +1,25 @@
# Animated circle with callback
In the task <info:task/animate-circle> an animated growing circle is shown.
Now let's say we need not just a circle, but to show a message inside it. The message should appear *after* the animation is complete (the circle is fully grown), otherwise it would look ugly.
In the solution of the task, the function `showCircle(cx, cy, radius)` draws the circle, but gives no way to track when it's ready.
Add a callback argument: `showCircle(cx, cy, radius, callback)` to be called when the animation is complete. The `callback` should receive the circle `<div>` as an argument.
Here's the example:
```js
showCircle(150, 150, 100, div => {
div.classList.add('message-ball');
div.append("Hello, world!");
});
```
Demo:
[iframe src="solution" height=260]
Take the solution of the task <info:task/animate-circle> as the base.

View file

@ -1,13 +1,6 @@
# Callback hell # Callback hell
Many things that we do in JavaScript are asynchronous. We initiate a process, but it finishes later.
The most obvious example is `setTimeout`, but there are others, like making network requests, performing animations and so on.
[cut]
## Callbacks
Consider this function `loadScript(src)` that loads a script: Consider this function `loadScript(src)` that loads a script:
@ -28,19 +21,28 @@ We can use it like this:
loadScript('/my/script.js'); loadScript('/my/script.js');
``` ```
The function is asynchronous: the script starts loading now, but finishes later. The function is *asynchronous*: the script starts loading now, but finishes later.
```smart header="Synchronous vs asynchronous"
"Synchonous" and "asynchronous" are general programming terms, not specific to JavaScript. "Synchonous" and "asynchronous" are general programming terms, not specific to JavaScript.
A *synchronous* action suspends the execution until it's completed. For instance, `alert` and `prompt` are synchronous: the program may not continue until they are finished. A *synchronous* action suspends the execution until it's completed. For instance, a call to `alert` or `prompt` is synchronous: the program may not continue until it's finished.
An *asynchronous* action allows the program to continue while it's in progress. For instance, `loadScript` in the example above initiates the script loading, but does not suspend the execution. Other commands may execute while the script is loading. ```js
let age = prompt("How old are you", 20);
// the execution of the code below awaits for the prompt to finish
``` ```
As of now, `loadScript` provides no way to track the load end. How can we execute our own code after the script is loaded? An *asynchronous* action allows the program to continue while it's in progress. For instance, a call to `loadScript` is asynchronous. It initiates the script loading, but does not suspend the execution. Other commands may execute while the script is loading:
Let's allow that by adding a custom function as a second argument to `loadScript`, that should execute at that moment: ```js
loadScript('/my/script.js');
// the execution does not wait for the script loading to finish,
// it just goes on
```
As of now, `loadScript` provides no way to track the load completion. The script loads and eventually runs.
Let's add a `callback` function as a second argument to `loadScript`, that should execute at when the script is loaded.
```js ```js
function loadScript(src, callback) { function loadScript(src, callback) {
@ -53,15 +55,25 @@ function loadScript(src, callback) {
} }
``` ```
Now when we want to load a script and then do something, we can call: Now we're able to load a script and run our code that can use new functions from it, like here:
```js ```js run
loadScript('/my/script.js', function(script) { function loadScript(src, callback) {
alert(`Cool, the ${script.src} is loaded, let's use it`); let script = document.createElement('script');
script.src = src;
script.onload = () => callback(script);
document.head.append(script);
}
*!*
loadScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js', function(script) {
alert(`Cool, the ${script.src} is loaded`);
alert( _ ); // function declared in the loaded script
}); });
*/!*
``` ```
...And it works, shows the `alert` after the script is loaded. That's called a "callback style" of asynchronous programming. A function that does something asynchronously provides a callback argument where we put the code to run after it's complete.
## Callback in callback ## Callback in callback
@ -92,24 +104,22 @@ loadScript('/my/script.js', function(script) {
loadScript('/my/script2.js', function(script) { loadScript('/my/script2.js', function(script) {
if (something) {
*!* *!*
loadScript('/my/script3.js', function(script) { loadScript('/my/script3.js', function(script) {
// ...continue after all scripts are loaded // ...continue after all scripts are loaded
}); });
*/!* */!*
}
}) })
}); });
``` ```
As you can see, a new asynchronous action means one more nesting level. As we can see, a new asynchronous action means one more nesting level. So the code becomes deeper and deeper.
## Handling errors ## Handling errors
In this example we didn't consider errors. What if a script loading failed with an error? Our callback should be able to react on that. In examples above we didn't consider errors. What if a script loading failed with an error? Our callback should be able to react on that.
Here's an improved version of `loadScript` that tracks loading errors: Here's an improved version of `loadScript` that tracks loading errors:
@ -140,12 +150,14 @@ loadScript('/my/script.js', function(error, script) {
}); });
``` ```
The first argument of `callback` is reserved for errors, and the second argument is for the successful result. The convention is:
1. The first argument of `callback` is reserved for an error if it occurs.
2. The second argument (and successive ones if needed) are for the successful result.
So the single `callback` function is used both for reporting errors and passing back results.
## Pyramid of doom ## Pyramid of doom
What we've just seen is called a "callback-based" approach to asynchronous programming. We pass a function, and it should run after the process is complete: with an error or a successful result.
From the first look it's a viable way of asynchronous coding. And indeed it is. For one or maybe two nested calls it looks fine. From the first look it's a viable way of asynchronous coding. And indeed it is. For one or maybe two nested calls it looks fine.
But for multiple asynchronous actions that follow one after another we'll have a code like this: But for multiple asynchronous actions that follow one after another we'll have a code like this:
@ -191,4 +203,38 @@ That's sometimes called "callback hell" or "pyramid of doom".
The pyramid grows to the right with every asynchronous action. Soon it spirales out of control. The pyramid grows to the right with every asynchronous action. Soon it spirales out of control.
Fortunately, there are ways to evade such pyramids. One of them is using "promises", we'll study them in the next chapters. In simple cases we can evade the problem by making every action a standalone function, like this:
```js
loadScript('1.js', step1);
function step1(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('2.js', step2);
}
}
function step2(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('3.js', step3);
}
}
function step3(error, script) {
if (error) {
handleError(error);
} else {
// ...continue after all scripts are loaded (*)
}
};
```
See? There's no deep nesting now, because we moved every function to the top. But the code looks like a torn apart spreadsheet. We need to eye-jump between pieces while reading it. The functions `step*` have no use, they are only created to evade the "pyramid of doom".
Luckily, there are other ways to evade such pyramids. Most modern code makes use of "promises", we'll study them in the next chapter.

View file

@ -0,0 +1,3 @@
function one() {
alert(1);
}

View file

@ -2,6 +2,13 @@
A promise is an object of the built-in `Promise` class. It has the meaning of the "delayed result". A promise is an object of the built-in `Promise` class. It has the meaning of the "delayed result".
The promise object has two inernal fields in it:
- `state` -- one of: "pending", "fulfilled", "rejected".
- `result` -- when `new Promise` is created.
The constructor syntax is: The constructor syntax is:
```js ```js

View file

@ -253,7 +253,7 @@ new Promise(function(resolve, reject) {
``` ```
### Rethrowing ## Rethrowing
As we already noticed, `.catch` is like `try..catch`. We may have as many `.then` as we want, and then use a single `.catch` at the end to handle errors in all of them. As we already noticed, `.catch` is like `try..catch`. We may have as many `.then` as we want, and then use a single `.catch` at the end to handle errors in all of them.
@ -308,7 +308,7 @@ new Promise(function(resolve, reject) {
}).then(alert); // undefined }).then(alert); // undefined
``` ```
### Unhandled rejections ## Unhandled rejections
What if we forget to handle an error? What if we forget to handle an error?
@ -343,6 +343,7 @@ Usually that means that the code is bad. Most JavaScript engines track such situ
// open in a new window to see in action // open in a new window to see in action
window.addEventListener('unhandledrejection', function(event) { window.addEventListener('unhandledrejection', function(event) {
// the event object has two special properties:
alert(event.promise); // the promise that generated the error alert(event.promise); // the promise that generated the error
alert(event.reason); // the error itself (Whoops!) alert(event.reason); // the error itself (Whoops!)
}); });
@ -359,53 +360,3 @@ new Promise(function() {
``` ```
In non-browser environments there's also a similar event, so we can always track unhandled errors in promises. In non-browser environments there's also a similar event, so we can always track unhandled errors in promises.
An object that has a method called `.then` is called a "thenable".
Instead of checking if something is `instanceof Promise`, we should usually check it for being thenable, and if it is, then treat it as a promise ("duck typing").
JavaScript specification also checks the value returned by a handler for being a thenable, not exactly a promise, when it decides whether to pass it along the chain or wait for the result. So in the examples above we could use custom thenables instead of `Promise` instances.
For instance, native promises give no way to "abort" the execution. The `loadScript` above cannot "cancel" script loading, just because there's no `.abort` method on promises, we can only listen for the state change using `.then/catch`.
## Extending promises, thenables
Promises are very simple by design. One of the thing they miss is the ability to cancel the process.
For instance, `loadScript(src)` in previous examples returns a promise that allows to track success/failure of the loading. But can we abort it? No.
We can inherit from `Promise` to introduce such functionality, like this:
// TODO: NOT WORKING AS INTENDED?
```js run
function loadScript(src) {
let script = document.createElement('script');
script.src = src;
let promise = new Promise(function(resolve, reject) {
script.onload = () => resolve(script);
*!*
script.onerror = err => reject(new Error("Script load error: " + src)); // (*)
*/!*
});
document.head.append(script);
promise.abort = () => script.remove();
return promise;
}
let promise = loadScript("/article/promise-chaining/one.js");
promise.then(alert);
promise.abort();
```
## Inheriting from promise, thenables, promise api, async/await

View file

@ -1 +1,3 @@
let one = 1; function one() {
alert(1);
}

View file

@ -0,0 +1,15 @@
# Promises API
Let's meet more functions and methods for promises.
Keywords `async` and `await` provide a more elegant way to write the code using promises.
## Async functions
The `async` function is like a regular one, but it wraps a returned value in a `Promise`.
Nowadays, promises are de-facto standard for asynchronous actions, when we need to

View file

@ -0,0 +1,11 @@
# Async/await
Keywords `async` and `await` provide a more elegant way to write the code using promises.
## Async functions
The `async` function is like a regular one, but it wraps a returned value in a `Promise`.
Nowadays, promises are de-facto standard for asynchronous actions, when we need to

58
8-async/whereto.txt Normal file
View file

@ -0,0 +1,58 @@
## Extending promises, thenables
Promises are very simple by design. One of the thing they miss is the ability to cancel the process.
For instance, `loadScript(src)` in previous examples returns a promise that allows to track success/failure of the loading. But can we abort it? No.
We can inherit from `Promise` to introduce such functionality, like this:
```js run
function loadScript(src) {
let script = document.createElement('script');
alert(1);
script.src = src;
let promise = new Promise(function(resolve, reject) {
script.onload = () => { alert('onload'); resolve(script); }
*!*
script.onerror = err => reject(new Error("Script load error: " + src)); // (*)
*/!*
});
alert(2);
document.head.append(script);
alert(3);
promise.abort = () => {
script.src = 'javascript:""
';
};
return promise;
}
let promise = loadScript("/article/promise-chaining/one.js?speed=0");
promise.then(alert);
promise.abort();
alert(4);
```
## Inheriting from promise, thenables, promise api, async/await
--------
An object that has a method called `.then` is called a "thenable".
Instead of checking if something is `instanceof Promise`, we should usually check it for being thenable, and if it is, then treat it as a promise ("duck typing").
JavaScript specification also checks the value returned by a handler for being a thenable, not exactly a promise, when it decides whether to pass it along the chain or wait for the result. So in the examples above we could use custom thenables instead of `Promise` instances.
For instance, native promises give no way to "abort" the execution. The `loadScript` above cannot "cancel" script loading, just because there's no `.abort` method on promises, we can only listen for the state change using `.then/catch`.