refactoring, modules

This commit is contained in:
Ilya Kantor 2019-02-26 18:48:08 +03:00
parent b8f6d96990
commit a16814aaf1
112 changed files with 484 additions and 1825 deletions

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

@ -0,0 +1,271 @@
# Introduction: callbacks
Many actions in JavaScript are *asynchronous*.
For instance, take a look at the function `loadScript(src)`:
```js
function loadScript(src) {
let script = document.createElement('script');
script.src = src;
document.head.append(script);
}
```
The purpose of the function is to load a new script. When it adds the `<script src="…">` to the document, the browser loads and executes it.
We can use it like this:
```js
// loads and executes the script
loadScript('/my/script.js');
```
The function is called "asynchronously," because the action (script loading) finishes not now, but later.
The call initiates the script loading, then the execution continues. While the script is loading, the code below may finish executing, and if the loading takes time, other scripts may run meanwhile too.
```js
loadScript('/my/script.js');
// the code below loadScript doesn't wait for the script loading to finish
// ...
```
Now let's say we want to use the new script when it loads. It probably declares new functions, so we'd like to run them.
But if we do that immediately after the `loadScript(…)` call, that wouldn't work:
```js
loadScript('/my/script.js'); // the script has "function newFunction() {…}"
*!*
newFunction(); // no such function!
*/!*
```
Naturally, the browser probably didn't have time to load the script. So the immediate call to the new function fails. As of now, the `loadScript` function doesn't provide a way to track the load completion. The script loads and eventually runs, that's all. But we'd like to know when it happens, to use new functions and variables from that script.
Let's add a `callback` function as a second argument to `loadScript` that should execute when the script loads:
```js
function loadScript(src, *!*callback*/!*) {
let script = document.createElement('script');
script.src = src;
*!*
script.onload = () => callback(script);
*/!*
document.head.append(script);
}
```
Now if we want to call new functions from the script, we should write that in the callback:
```js
loadScript('/my/script.js', function() {
// the callback runs after the script is loaded
newFunction(); // so now it works
...
});
```
That's the idea: the second argument is a function (usually anonymous) that runs when the action is completed.
Here's a runnable example with a real script:
```js run
function loadScript(src, callback) {
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', script => {
alert(`Cool, the ${script.src} is loaded`);
alert( _ ); // function declared in the loaded script
});
*/!*
```
That's called a "callback-based" style of asynchronous programming. A function that does something asynchronously should provide a `callback` argument where we put the function to run after it's complete.
Here we did it in `loadScript`, but of course, it's a general approach.
## Callback in callback
How to load two scripts sequentially: the first one, and then the second one after it?
The natural solution would be to put the second `loadScript` call inside the callback, like this:
```js
loadScript('/my/script.js', function(script) {
alert(`Cool, the ${script.src} is loaded, let's load one more`);
*!*
loadScript('/my/script2.js', function(script) {
alert(`Cool, the second script is loaded`);
});
*/!*
});
```
After the outer `loadScript` is complete, the callback initiates the inner one.
What if we want one more script...?
```js
loadScript('/my/script.js', function(script) {
loadScript('/my/script2.js', function(script) {
*!*
loadScript('/my/script3.js', function(script) {
// ...continue after all scripts are loaded
});
*/!*
})
});
```
So, every new action is inside a callback. That's fine for few actions, but not good for many, so we'll see other variants soon.
## Handling errors
In examples above we didn't consider errors. What if the script loading fails? Our callback should be able to react on that.
Here's an improved version of `loadScript` that tracks loading errors:
```js run
function loadScript(src, callback) {
let script = document.createElement('script');
script.src = src;
*!*
script.onload = () => callback(null, script);
script.onerror = () => callback(new Error(`Script load error for ${src}`));
*/!*
document.head.append(script);
}
```
It calls `callback(null, script)` for successful load and `callback(error)` otherwise.
The usage:
```js
loadScript('/my/script.js', function(error, script) {
if (error) {
// handle error
} else {
// script loaded successfully
}
});
```
Once again, the recipe that we used for `loadScript` is actually quite common. It's called the "error-first callback" style.
The convention is:
1. The first argument of the `callback` is reserved for an error if it occurs. Then `callback(err)` is called.
2. The second argument (and the next ones if needed) are for the successful result. Then `callback(null, result1, result2…)` is called.
So the single `callback` function is used both for reporting errors and passing back results.
## Pyramid of Doom
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 code like this:
```js
loadScript('1.js', function(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('2.js', function(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('3.js', function(error, script) {
if (error) {
handleError(error);
} else {
*!*
// ...continue after all scripts are loaded (*)
*/!*
}
});
}
})
}
});
```
In the code above:
1. We load `1.js`, then if there's no error.
2. We load `2.js`, then if there's no error.
3. We load `3.js`, then if there's no error -- do something else `(*)`.
As calls become more nested, the code becomes deeper and increasingly more difficult to manage, especially if we have a real code instead of `...`, that may include more loops, conditional statements and so on.
That's sometimes called "callback hell" or "pyramid of doom."
![](callback-hell.png)
The "pyramid" of nested calls grows to the right with every asynchronous action. Soon it spirals out of control.
So this way of coding isn't very good.
We can try to alleviate 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? It does the same, and there's no deep nesting now because we made every action a separate top-level function.
It works, but the code looks like a torn apart spreadsheet. It's difficult to read, and you probably noticed that. One needs to eye-jump between pieces while reading it. That's inconvenient, especially if the reader is not familiar with the code and doesn't know where to eye-jump.
Also, the functions named `step*` are all of single use, they are created only to avoid the "pyramid of doom." No one is going to reuse them outside of the action chain. So there's a bit of a namespace cluttering here.
We'd like to have something better.
Luckily, there are other ways to avoid such pyramids. One of the best ways is to use "promises," described in the next chapter.

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

View file

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

View file

@ -0,0 +1,3 @@
The output is: `1`.
The second call to `resolve` is ignored, because only the first call of `reject/resolve` is taken into account. Further calls are ignored.

View file

@ -0,0 +1,15 @@
# Re-resolve a promise?
What's the output of the code below?
```js
let promise = new Promise(function(resolve, reject) {
resolve(1);
setTimeout(() => resolve(2), 1000);
});
promise.then(alert);
```

View file

@ -0,0 +1,9 @@
```js run
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
delay(3000).then(() => alert('runs after 3 seconds'));
```
Please note that in this task `resolve` is called without arguments. We don't return any value from `delay`, just ensure the delay.

View file

@ -0,0 +1,14 @@
# Delay with a promise
The built-in function `setTimeout` uses callbacks. Create a promise-based alternative.
The function `delay(ms)` should return a promise. That promise should resolve after `ms` milliseconds, so that we can add `.then` to it, like this:
```js
function delay(ms) {
// your code
}
delay(3000).then(() => alert('runs after 3 seconds'));
```

View file

@ -0,0 +1,61 @@
<!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).then(div => {
div.classList.add('message-ball');
div.append("Hello, world!");
});
}
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);
return new Promise(resolve => {
setTimeout(() => {
div.style.width = radius * 2 + 'px';
div.style.height = radius * 2 + 'px';
div.addEventListener('transitionend', function handler() {
div.removeEventListener('transitionend', handler);
resolve(div);
});
}, 0);
})
}
</script>
</body>
</html>

View file

@ -0,0 +1,15 @@
# Animated circle with promise
Rewrite the `showCircle` function in the solution of the task <info:task/animate-circle-callback> so that it returns a promise instead of accepting a callback.
The new usage:
```js
showCircle(150, 150, 100).then(div => {
div.classList.add('message-ball');
div.append("Hello, world!");
});
```
Take the solution of the task <info:task/animate-circle-callback> as the base.

View file

@ -0,0 +1,310 @@
# Promise
Imagine that you're a top singer, and fans ask day and night for your upcoming single.
To get some relief, you promise to send it to them when it's published. You give your fans a list to which they can subscribe for updates. They can fill in their email addresses, so that when the song becomes available, all subscribed parties instantly receive it. And even if something goes very wrong, say, if plans to publish the song are cancelled, they will still be notified.
Everyone is happy, because the people don't crowd you any more, and fans, because they won't miss the single.
This is a real-life analogy for things we often have in programming:
1. A "producing code" that does something and takes time. For instance, the code loads a remote script. That's a "singer".
2. A "consuming code" that wants the result of the "producing code" once it's ready. Many functions may need that result. These are the "fans".
3. A *promise* is a special JavaScript object that links the "producing code" and the "consuming code" together. In terms of our analogy: this is the "subscription list". The "producing code" takes whatever time it needs to produce the promised result, and the "promise" makes that result available to all of the subscribed code when it's ready.
The analogy isn't terribly accurate, because JavaScript promises are more complex than a simple subscription list: they have additional features and limitations. But it's fine to begin with.
The constructor syntax for a promise object is:
```js
let promise = new Promise(function(resolve, reject) {
// executor (the producing code, "singer")
});
```
The function passed to `new Promise` is called the *executor*. When the promise is created, this executor function runs automatically. It contains the producing code, that should eventually produce a result. In terms of the analogy above: the executor is the "singer".
The resulting `promise` object has internal properties:
- `state` — initially "pending", then changes to either "fulfilled" or "rejected",
- `result` — an arbitrary value of your choosing, initially `undefined`.
When the executor finishes the job, it should call one of the functions that it gets as arguments:
- `resolve(value)` — to indicate that the job finished successfully:
- sets `state` to `"fulfilled"`,
- sets `result` to `value`.
- `reject(error)` — to indicate that an error occurred:
- sets `state` to `"rejected"`,
- sets `result` to `error`.
![](promise-resolve-reject.png)
Later we'll see how these changes become known to "fans".
Here's an example of a Promise constructor and a simple executor function with its "producing code" (the `setTimeout`):
```js run
let promise = new Promise(function(resolve, reject) {
// the function is executed automatically when the promise is constructed
// after 1 second signal that the job is done with the result "done!"
setTimeout(() => *!*resolve("done!")*/!*, 1000);
});
```
We can see two things by running the code above:
1. The executor is called automatically and immediately (by the `new Promise`).
2. The executor receives two arguments: `resolve` and `reject` — these functions are pre-defined by the JavaScript engine. So we don't need to create them. Instead, we should write the executor to call them when ready.
After one second of "processing" the executor calls `resolve("done")` to produce the result:
![](promise-resolve-1.png)
That was an example of a successful job completion, a "fulfilled promise".
And now an example of the executor rejecting the promise with an error:
```js
let promise = new Promise(function(resolve, reject) {
// after 1 second signal that the job is finished with an error
setTimeout(() => *!*reject(new Error("Whoops!"))*/!*, 1000);
});
```
![](promise-reject-1.png)
To summarize, the executor should do a job (something that takes time usually) and then call `resolve` or `reject` to change the state of the corresponding Promise object.
The Promise that is either resolved or rejected is called "settled", as opposed to a "pending" Promise.
````smart header="There can be only a single result or an error"
The executor should call only one `resolve` or one `reject`. The promise's state change is final.
All further calls of `resolve` and `reject` are ignored:
```js
let promise = new Promise(function(resolve, reject) {
resolve("done");
reject(new Error("…")); // ignored
setTimeout(() => resolve("…")); // ignored
});
```
The idea is that a job done by the executor may have only one result or an error.
Also, `resolve`/`reject` expect only one argument (or none) and will ignore additional arguments.
````
```smart header="Reject with `Error` objects"
In case something goes wrong, we can call `reject` with any type of argument (just like `resolve`). But it is recommended to use `Error` objects (or objects that inherit from `Error`). The reasoning for that will soon become apparent.
```
````smart header="Immediately calling `resolve`/`reject`"
In practice, an executor usually does something asynchronously and calls `resolve`/`reject` after some time, but it doesn't have to. We also can call `resolve` or `reject` immediately, like this:
```js
let promise = new Promise(function(resolve, reject) {
// not taking our time to do the job
resolve(123); // immediately give the result: 123
});
```
For instance, this might happen when we start to do a job but then see that everything has already been completed.
That's fine. We immediately have a resolved Promise, nothing wrong with that.
````
```smart header="The `state` and `result` are internal"
The properties `state` and `result` of the Promise object are internal. We can't directly access them from our "consuming code". We can use the methods `.then`/`.catch` for that. They are described below.
```
## Consumers: "then" and "catch"
A Promise object serves as a link between the executor (the "producing code" or "singer") and the consuming functions (the "fans"), which will receive the result or error. Consuming functions can be registered (subscribed) using the methods `.then` and `.catch`.
The syntax of `.then` is:
```js
promise.then(
function(result) { *!*/* handle a successful result */*/!* },
function(error) { *!*/* handle an error */*/!* }
);
```
The first argument of `.then` is a function that:
1. runs when the Promise is resolved, and
2. receives the result.
The second argument of `.then` is a function that:
1. runs when the Promise is rejected, and
2. receives the error.
For instance, here's the reaction to a successfuly resolved promise:
```js run
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve("done!"), 1000);
});
// resolve runs the first function in .then
promise.then(
*!*
result => alert(result), // shows "done!" after 1 second
*/!*
error => alert(error) // doesn't run
);
```
The first function was executed.
And in the case of a rejection -- the second one:
```js run
let promise = new Promise(function(resolve, reject) {
setTimeout(() => reject(new Error("Whoops!")), 1000);
});
// reject runs the second function in .then
promise.then(
result => alert(result), // doesn't run
*!*
error => alert(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`:
```js run
let promise = new Promise(resolve => {
setTimeout(() => resolve("done!"), 1000);
});
*!*
promise.then(alert); // shows "done!" after 1 second
*/!*
```
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:
```js run
let promise = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error("Whoops!")), 1000);
});
*!*
// .catch(f) is the same as promise.then(null, f)
promise.catch(alert); // shows "Error: Whoops!" after 1 second
*/!*
```
The call `.catch(f)` is a complete analog of `.then(null, f)`, it's just a shorthand.
````smart header="On settled promises `then` runs immediately"
If a promise is pending, `.then/catch` handlers wait for the result. Otherwise, if a promise has already settled, they execute immediately:
```js run
// an immediately resolved promise
let promise = new Promise(resolve => resolve("done!"));
promise.then(alert); // done! (shows up right now)
```
Some tasks may sometimes require time and sometimes finish immediately. The good thing is: the `.then` handler is guaranteed to run in both cases.
````
````smart header="Handlers of `.then`/`.catch` are always asynchronous"
Even when the Promise is immediately resolved, code which occurs on lines *below* your `.then`/`.catch` may still execute first.
The JavaScript engine has an internal execution queue which gets all `.then/catch` handlers.
But it only looks into that queue when the current execution is finished.
In other words, `.then/catch` handlers are pending execution until the engine is done with the current code.
For instance, here:
```js run
// an "immediately" resolved Promise
const executor = resolve => resolve("done!");
const promise = new Promise(executor);
promise.then(alert); // this alert shows last (*)
alert("code finished"); // this alert shows first
```
The promise becomes settled immediately, but the engine first finishes the current code, calls `alert`, and only *afterwards* looks into the queue to run `.then` handler.
So the code *after* `.then` ends up always running *before* the Promise's subscribers, even in the case of an immediately-resolved Promise.
Usually that's unimportant, but in some scenarios the order may matter a great deal.
````
Next, let's see more practical examples of how promises can help us to write asynchronous code.
## Example: loadScript
We've got the `loadScript` function for loading a script from the previous chapter.
Here's the callback-based variant, just to remind us of it:
```js
function loadScript(src, callback) {
let script = document.createElement('script');
script.src = src;
script.onload = () => callback(null, script);
script.onerror = () => callback(new Error(`Script load error ` + src));
document.head.append(script);
}
```
Let's rewrite it using Promises.
The new function `loadScript` will not require a callback. Instead, it will create and return a Promise object that resolves when the loading is complete. The outer code can add handlers (subscribing functions) to it using `.then`:
```js run
function loadScript(src) {
return new Promise(function(resolve, reject) {
let script = document.createElement('script');
script.src = src;
script.onload = () => resolve(script);
script.onerror = () => reject(new Error("Script load error: " + src));
document.head.append(script);
});
}
```
Usage:
```js run
let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js");
promise.then(
script => alert(`${script.src} is loaded!`),
error => alert(`Error: ${error.message}`)
);
promise.then(script => alert('One more handler to do something else!'));
```
We can immediately see a few benefits over the callback-based pattern:
```compare minus="Callbacks" plus="Promises"
- We must have a ready `callback` function when calling `loadScript`. In other words, we must know what to do with the result *before* `loadScript` is called.
- There can be only one callback.
+ Promises allow us to do things in the natural order. First, we run `loadScript`, and `.then` we write what to do with the result.
+ We can call `.then` on a Promise as many times as we want. Each time, we're adding a new "fan", a new subscribing function, to the "subscription list". More about this in the next section: [Promise Chaining](/promise-chaining).
```
So Promises already give us better code flow and flexibility. But there's more. We'll see that in the next chapters.

View file

@ -0,0 +1,13 @@
<script>
function loadScript(src) {
return new Promise(function(resolve, reject) {
let script = document.createElement('script');
script.src = src;
script.onload = () => resolve(script);
script.onerror = () => reject(new Error("Script load error: " + src));
document.head.append(script);
});
}
</script>

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View file

@ -0,0 +1,20 @@
The short answer is: **no, they are not the equal**:
The difference is that if an error happens in `f1`, then it is handled by `.catch` here:
```js run
promise
.then(f1)
.catch(f2);
```
...But not here:
```js run
promise
.then(f1, f2);
```
That's because an error is passed down the chain, and in the second code piece there's no chain below `f1`.
In other words, `.then` passes results/errors to the next `.then/catch`. So in the first example, there's a `catch` below, and in the second one -- there isn't, so the error is unhandled.

View file

@ -0,0 +1,12 @@
# Promise: then versus catch
Are these code fragments equal? In other words, do they behave the same way in any circumstances, for any handler functions?
```js
promise.then(f1).catch(f2);
```
Versus;
```js
promise.then(f1, f2);
```

View file

@ -0,0 +1,13 @@
The answer is: **no, it won't**:
```js run
new Promise(function(resolve, reject) {
setTimeout(() => {
throw new Error("Whoops!");
}, 1000);
}).catch(alert);
```
As said in the chapter, there's an "implicit `try..catch`" around the function code. So all synchronous errors are handled.
But here the error is generated not while the executor is running, but later. So the promise can't handle it.

View file

@ -0,0 +1,11 @@
# Error in setTimeout
What do you think? Will the `.catch` trigger? Explain your answer.
```js
new Promise(function(resolve, reject) {
setTimeout(() => {
throw new Error("Whoops!");
}, 1000);
}).catch(alert);
```

View file

@ -0,0 +1,684 @@
# Promises chaining
Let's return to the problem mentioned in the chapter <info:callbacks>.
- We have a sequence of asynchronous tasks to be done one after another. For instance, loading scripts.
- How to code it well?
Promises provide a couple of recipes to do that.
In this chapter we cover promise chaining.
It looks like this:
```js run
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000); // (*)
}).then(function(result) { // (**)
alert(result); // 1
return result * 2;
}).then(function(result) { // (***)
alert(result); // 2
return result * 2;
}).then(function(result) {
alert(result); // 4
return result * 2;
});
```
The idea is that the result is passed through the chain of `.then` handlers.
Here the flow is:
1. The initial promise resolves in 1 second `(*)`,
2. Then the `.then` handler is called `(**)`.
3. The value that it returns is passed to the next `.then` handler `(***)`
4. ...and so on.
As the result is passed along the chain of handlers, we can see a sequence of `alert` calls: `1` -> `2` -> `4`.
![](promise-then-chain.png)
The whole thing works, because a call to `promise.then` returns a promise, so that we can call the next `.then` on it.
When a handler returns a value, it becomes the result of that promise, so the next `.then` is called with it.
To make these words more clear, here's the start of the chain:
```js run
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
}).then(function(result) {
alert(result);
return result * 2; // <-- (1)
}) // <-- (2)
// .then…
```
The value returned by `.then` is a promise, that's why we are able to add another `.then` at `(2)`. When the value is returned in `(1)`, that promise becomes resolved, so the next handler runs with the value.
Unlike the chaining, technically we can also add many `.then` to a single promise, like this:
```js run
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
```
...But that's a totally different thing. Here's the picture (compare it with the chaining above):
![](promise-then-many.png)
All `.then` on the same promise get the same result -- the result of that promise. So in the code above all `alert` show the same: `1`. There is no result-passing between them.
In practice we rarely need multiple handlers for one promise. Chaining is used much more often.
## Returning promises
Normally, a value returned by a `.then` handler is immediately passed to the next handler. But there's an exception.
If the returned value is a promise, then the further execution is suspended until it settles. After that, the result of that promise is given to the next `.then` handler.
For instance:
```js run
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
}).then(function(result) {
alert(result); // 1
*!*
return new Promise((resolve, reject) => { // (*)
setTimeout(() => resolve(result * 2), 1000);
});
*/!*
}).then(function(result) { // (**)
alert(result); // 2
return new Promise((resolve, reject) => {
setTimeout(() => resolve(result * 2), 1000);
});
}).then(function(result) {
alert(result); // 4
});
```
Here the first `.then` shows `1` returns `new Promise(…)` in the line `(*)`. After one second it resolves, and the result (the argument of `resolve`, here it's `result*2`) is passed on to handler of the second `.then` in the line `(**)`. It shows `2` and does the same thing.
So the output is again 1 -> 2 -> 4, but now with 1 second delay between `alert` calls.
Returning promises allows us to build chains of asynchronous actions.
## Example: loadScript
Let's use this feature with `loadScript` to load scripts one by one, in sequence:
```js run
loadScript("/article/promise-chaining/one.js")
.then(function(script) {
return loadScript("/article/promise-chaining/two.js");
})
.then(function(script) {
return loadScript("/article/promise-chaining/three.js");
})
.then(function(script) {
// use functions declared in scripts
// to show that they indeed loaded
one();
two();
three();
});
```
Here each `loadScript` call returns a promise, and the next `.then` runs when it resolves. Then it initiates the loading of the next script. So scripts are loaded one after another.
We can add more asynchronous actions to the chain. Please note that code is still "flat", it grows down, not to the right. There are no signs of "pyramid of doom".
Please note that technically it is also possible to write `.then` directly after each promise, without returning them, like this:
```js run
loadScript("/article/promise-chaining/one.js").then(function(script1) {
loadScript("/article/promise-chaining/two.js").then(function(script2) {
loadScript("/article/promise-chaining/three.js").then(function(script3) {
// this function has access to variables script1, script2 and script3
one();
two();
three();
});
});
});
```
This code does the same: loads 3 scripts in sequence. But it "grows to the right". So we have the same problem as with callbacks. Use chaining (return promises from `.then`) to evade it.
Sometimes it's ok to write `.then` directly, because the nested function has access to the outer scope (here the most nested callback has access to all variables `scriptX`), but that's an exception rather than a rule.
````smart header="Thenables"
To be precise, `.then` may return an arbitrary "thenable" object, and it will be treated the same way as a promise.
A "thenable" object is any object with a method `.then`.
The idea is that 3rd-party libraries may implement "promise-compatible" objects of their own. They can have extended set of methods, but also be compatible with native promises, because they implement `.then`.
Here's an example of a thenable object:
```js run
class Thenable {
constructor(num) {
this.num = num;
}
then(resolve, reject) {
alert(resolve); // function() { native code }
// resolve with this.num*2 after the 1 second
setTimeout(() => resolve(this.num * 2), 1000); // (**)
}
}
new Promise(resolve => resolve(1))
.then(result => {
return new Thenable(result); // (*)
})
.then(alert); // shows 2 after 1000ms
```
JavaScript checks the object returned by `.then` handler in the line `(*)`: if it has a callable method named `then`, then it calls that method providing native functions `resolve`, `reject` as arguments (similar to executor) and waits until one of them is called. In the example above `resolve(2)` is called after 1 second `(**)`. Then the result is passed further down the chain.
This feature allows to integrate custom objects with promise chains without having to inherit from `Promise`.
````
## Bigger example: fetch
In frontend programming promises are often used for network requests. So let's see an extended example of that.
We'll use the [fetch](mdn:api/WindowOrWorkerGlobalScope/fetch) method to load the information about the user from the remote server. The method is quite complex, it has many optional parameters, but the basic usage is quite simple:
```js
let promise = fetch(url);
```
This makes a network request to the `url` and returns a promise. The promise resolves with a `response` object when the remote server responds with headers, but *before the full response is downloaded*.
To read the full response, we should call a method `response.text()`: it returns a promise that resolves when the full text downloaded from the remote server, with that text as a result.
The code below makes a request to `user.json` and loads its text from the server:
```js run
fetch('/article/promise-chaining/user.json')
// .then below runs when the remote server responds
.then(function(response) {
// response.text() returns a new promise that resolves with the full response text
// when we finish downloading it
return response.text();
})
.then(function(text) {
// ...and here's the content of the remote file
alert(text); // {"name": "iliakan", isAdmin: true}
});
```
There is also a method `response.json()` that reads the remote data and parses it as JSON. In our case that's even more convenient, so let's switch to it.
We'll also use arrow functions for brevity:
```js run
// same as above, but response.json() parses the remote content as JSON
fetch('/article/promise-chaining/user.json')
.then(response => response.json())
.then(user => alert(user.name)); // iliakan
```
Now let's do something with the loaded user.
For instance, we can make one more request to github, load the user profile and show the avatar:
```js run
// Make a request for user.json
fetch('/article/promise-chaining/user.json')
// Load it as json
.then(response => response.json())
// Make a request to github
.then(user => fetch(`https://api.github.com/users/${user.name}`))
// Load the response as json
.then(response => response.json())
// Show the avatar image (githubUser.avatar_url) for 3 seconds (maybe animate it)
.then(githubUser => {
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => img.remove(), 3000); // (*)
});
```
The code works, see comments about the details, but it should be quite self-descriptive. Although, there's a potential problem in it, a typical error of those who begin to use promises.
Look at the line `(*)`: how can we do something *after* the avatar has finished showing and gets removed? For instance, we'd like to show a form for editing that user or something else. As of now, there's no way.
To make the chain extendable, we need to return a promise that resolves when the avatar finishes showing.
Like this:
```js run
fetch('/article/promise-chaining/user.json')
.then(response => response.json())
.then(user => fetch(`https://api.github.com/users/${user.name}`))
.then(response => response.json())
*!*
.then(githubUser => new Promise(function(resolve, reject) {
*/!*
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => {
img.remove();
*!*
resolve(githubUser);
*/!*
}, 3000);
}))
// triggers after 3 seconds
.then(githubUser => alert(`Finished showing ${githubUser.name}`));
```
Now right after `setTimeout` runs `img.remove()`, it calls `resolve(githubUser)`, thus passing the control to the next `.then` in the chain and passing forward the user data.
As a rule, an asynchronous action should always return a promise.
That makes possible to plan actions after it. Even if we don't plan to extend the chain now, we may need it later.
Finally, we can split the code into reusable functions:
```js run
function loadJson(url) {
return fetch(url)
.then(response => response.json());
}
function loadGithubUser(name) {
return fetch(`https://api.github.com/users/${name}`)
.then(response => response.json());
}
function showAvatar(githubUser) {
return new Promise(function(resolve, reject) {
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => {
img.remove();
resolve(githubUser);
}, 3000);
});
}
// Use them:
loadJson('/article/promise-chaining/user.json')
.then(user => loadGithubUser(user.name))
.then(showAvatar)
.then(githubUser => alert(`Finished showing ${githubUser.name}`));
// ...
```
## Error handling
Asynchronous actions may sometimes fail: in case of an error the corresponding promise becomes rejected. For instance, `fetch` fails if the remote server is not available. We can use `.catch` to handle errors (rejections).
Promise chaining is great at that aspect. When a promise rejects, the control jumps to the closest rejection handler down the chain. That's very convenient in practice.
For instance, in the code below the URL is wrong (no such server) and `.catch` handles the error:
```js run
*!*
fetch('https://no-such-server.blabla') // rejects
*/!*
.then(response => response.json())
.catch(err => alert(err)) // TypeError: failed to fetch (the text may vary)
```
Or, maybe, everything is all right with the server, but the response is not a valid JSON:
```js run
fetch('/') // fetch works fine now, the server responds successfully
*!*
.then(response => response.json()) // rejects: the page is HTML, not a valid json
*/!*
.catch(err => alert(err)) // SyntaxError: Unexpected token < in JSON at position 0
```
In the example below we append `.catch` to handle all errors in the avatar-loading-and-showing chain:
```js run
fetch('/article/promise-chaining/user.json')
.then(response => response.json())
.then(user => fetch(`https://api.github.com/users/${user.name}`))
.then(response => response.json())
.then(githubUser => new Promise(function(resolve, reject) {
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => {
img.remove();
resolve(githubUser);
}, 3000);
}))
.catch(error => alert(error.message));
```
Here `.catch` doesn't trigger at all, because there are no errors. But if any of the promises above rejects, then it would execute.
## Implicit try..catch
The code of the executor and promise handlers has an "invisible `try..catch`" around it. If an error happens, it gets caught and treated as a rejection.
For instance, this code:
```js run
new Promise(function(resolve, reject) {
*!*
throw new Error("Whoops!");
*/!*
}).catch(alert); // Error: Whoops!
```
...Works the same way as this:
```js run
new Promise(function(resolve, reject) {
*!*
reject(new Error("Whoops!"));
*/!*
}).catch(alert); // Error: Whoops!
```
The "invisible `try..catch`" around the executor automatically catches the error and treats it as a rejection.
That's so not only in the executor, but in handlers as well. If we `throw` inside `.then` handler, that means a rejected promise, so the control jumps to the nearest error handler.
Here's an example:
```js run
new Promise(function(resolve, reject) {
resolve("ok");
}).then(function(result) {
*!*
throw new Error("Whoops!"); // rejects the promise
*/!*
}).catch(alert); // Error: Whoops!
```
That's so not only for `throw`, but for any errors, including programming errors as well:
```js run
new Promise(function(resolve, reject) {
resolve("ok");
}).then(function(result) {
*!*
blabla(); // no such function
*/!*
}).catch(alert); // ReferenceError: blabla is not defined
```
As a side effect, the final `.catch` not only catches explicit rejections, but also occasional errors in the handlers above.
## Rethrowing
As we already noticed, `.catch` behaves 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.
In a regular `try..catch` we can analyze the error and maybe rethrow it if can't handle. The same thing is possible for promises. If we `throw` inside `.catch`, then the control goes to the next closest error handler. And if we handle the error and finish normally, then it continues to the closest successful `.then` handler.
In the example below the `.catch` successfully handles the error:
```js run
// the execution: catch -> then
new Promise(function(resolve, reject) {
throw new Error("Whoops!");
}).catch(function(error) {
alert("The error is handled, continue normally");
}).then(() => alert("Next successful handler runs"));
```
Here the `.catch` block finishes normally. So the next successful handler is called. Or it could return something, that would be the same.
...And here the `.catch` block analyzes the error and throws it again:
```js run
// the execution: catch -> catch -> then
new Promise(function(resolve, reject) {
throw new Error("Whoops!");
}).catch(function(error) { // (*)
if (error instanceof URIError) {
// handle it
} else {
alert("Can't handle such error");
*!*
throw error; // throwing this or another error jumps to the next catch
*/!*
}
}).then(function() {
/* never runs here */
}).catch(error => { // (**)
alert(`The unknown error has occurred: ${error}`);
// don't return anything => execution goes the normal way
});
```
The handler `(*)` catches the error and just can't handle it, because it's not `URIError`, so it throws it again. Then the execution jumps to the next `.catch` down the chain `(**)`.
In the section below we'll see a practical example of rethrowing.
## Fetch error handling example
Let's improve error handling for the user-loading example.
The promise returned by [fetch](mdn:api/WindowOrWorkerGlobalScope/fetch) rejects when it's impossible to make a request. For instance, a remote server is not available, or the URL is malformed. But if the remote server responds with error 404, or even error 500, then it's considered a valid response.
What if the server returns a non-JSON page with error 500 in the line `(*)`? What if there's no such user, and github returns a page with error 404 at `(**)`?
```js run
fetch('no-such-user.json') // (*)
.then(response => response.json())
.then(user => fetch(`https://api.github.com/users/${user.name}`)) // (**)
.then(response => response.json())
.catch(alert); // SyntaxError: Unexpected token < in JSON at position 0
// ...
```
As of now, the code tries to load the response as JSON no matter what and dies with a syntax error. You can see that by running the example above, as the file `no-such-user.json` doesn't exist.
That's not good, because the error just falls through the chain, without details: what failed and where.
So let's add one more step: we should check the `response.status` property that has HTTP status, and if it's not 200, then throw an error.
```js run
class HttpError extends Error { // (1)
constructor(response) {
super(`${response.status} for ${response.url}`);
this.name = 'HttpError';
this.response = response;
}
}
function loadJson(url) { // (2)
return fetch(url)
.then(response => {
if (response.status == 200) {
return response.json();
} else {
throw new HttpError(response);
}
})
}
loadJson('no-such-user.json') // (3)
.catch(alert); // HttpError: 404 for .../no-such-user.json
```
1. We make a custom class for HTTP Errors to distinguish them from other types of errors. Besides, the new class has a constructor that accepts the `response` object and saves it in the error. So error-handling code will be able to access it.
2. Then we put together the requesting and error-handling code into a function that fetches the `url` *and* treats any non-200 status as an error. That's convenient, because we often need such logic.
3. Now `alert` shows better message.
The great thing about having our own class for errors is that we can easily check for it in error-handling code.
For instance, we can make a request, and then if we get 404 -- ask the user to modify the information.
The code below loads a user with the given name from github. If there's no such user, then it asks for the correct name:
```js run
function demoGithubUser() {
let name = prompt("Enter a name?", "iliakan");
return loadJson(`https://api.github.com/users/${name}`)
.then(user => {
alert(`Full name: ${user.name}.`); // (1)
return user;
})
.catch(err => {
*!*
if (err instanceof HttpError && err.response.status == 404) { // (2)
*/!*
alert("No such user, please reenter.");
return demoGithubUser();
} else {
throw err;
}
});
}
demoGithubUser();
```
Here:
1. If `loadJson` returns a valid user object, then the name is shown `(1)`, and the user is returned, so that we can add more user-related actions to the chain. In that case the `.catch` below is ignored, everything's very simple and fine.
2. Otherwise, in case of an error, we check it in the line `(2)`. Only if it's indeed the HTTP error, and the status is 404 (Not found), we ask the user to reenter. For other errors -- we don't know how to handle, so we just rethrow them.
## Unhandled rejections
What happens when an error is not handled? For instance, after the rethrow as in the example above. Or if we forget to append an error handler to the end of the chain, like here:
```js untrusted run refresh
new Promise(function() {
noSuchFunction(); // Error here (no such function)
}); // no .catch attached
```
Or here:
```js untrusted run refresh
// a chain of promises without .catch at the end
new Promise(function() {
throw new Error("Whoops!");
}).then(function() {
// ...something...
}).then(function() {
// ...something else...
}).then(function() {
// ...but no catch after it!
});
```
In case of an error, the promise state becomes "rejected", and the execution should jump to the closest rejection handler. But there is no such handler in the examples above. So the error gets "stuck".
In practice, that's usually because of the bad code. Indeed, how come there's no error handling?
Most JavaScript engines track such situations and generate a global error in that case. We can see it in the console.
In the browser we can catch it using the event `unhandledrejection`:
```js run
*!*
window.addEventListener('unhandledrejection', function(event) {
// the event object has two special properties:
alert(event.promise); // [object Promise] - the promise that generated the error
alert(event.reason); // Error: Whoops! - the unhandled error object
});
*/!*
new Promise(function() {
throw new Error("Whoops!");
}); // no catch to handle the error
```
The event is the part of the [HTML standard](https://html.spec.whatwg.org/multipage/webappapis.html#unhandled-promise-rejections). Now if an error occurs, and there's no `.catch`, the `unhandledrejection` handler triggers: the `event` object has the information about the error, so we can do something with it.
Usually such errors are unrecoverable, so our best way out is to inform the user about the problem and probably report about the incident to the server.
In non-browser environments like Node.JS there are other similar ways to track unhandled errors.
## Summary
To summarize, `.then/catch(handler)` returns a new promise that changes depending on what handler does:
1. If it returns a value or finishes without a `return` (same as `return undefined`), then the new promise becomes resolved, and the closest resolve handler (the first argument of `.then`) is called with that value.
2. If it throws an error, then the new promise becomes rejected, and the closest rejection handler (second argument of `.then` or `.catch`) is called with it.
3. If it returns a promise, then JavaScript waits until it settles and then acts on its outcome the same way.
The picture of how the promise returned by `.then/catch` changes:
![](promise-handler-variants.png)
The smaller picture of how handlers are called:
![](promise-handler-variants-2.png)
In the examples of error handling above the `.catch` was always the last in the chain. In practice though, not every promise chain has a `.catch`. Just like regular code is not always wrapped in `try..catch`.
We should place `.catch` exactly in the places where we want to handle errors and know how to handle them. Using custom error classes can help to analyze errors and rethrow those that we can't handle.
For errors that fall outside of our scope we should have the `unhandledrejection` event handler (for browsers, and analogs for other environments). Such unknown errors are usually unrecoverable, so all we should do is to inform the user and probably report to our server about the incident.

View file

@ -0,0 +1,3 @@
function getMessage() {
return "Hello, world!";
}

View file

@ -0,0 +1,41 @@
<script>
function loadScript(src) {
return new Promise(function(resolve, reject) {
let script = document.createElement('script');
script.src = src;
script.onload = () => resolve(script);
script.onerror = () => reject(new Error("Script load error: " + src));
document.head.append(script);
});
}
class HttpError extends Error {
constructor(response) {
super(`${response.status} for ${response.url}`);
this.name = 'HttpError';
this.response = response;
}
}
function loadJson(url) {
return fetch(url)
.then(response => {
if (response.status == 200) {
return response.json();
} else {
throw new HttpError(response);
}
})
}
</script>
<style>
.promise-avatar-example {
border-radius: 50%;
position: fixed;
left: 10px;
top: 10px;
}
</style>

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View file

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

View file

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

View file

@ -0,0 +1,4 @@
{
"name": "iliakan",
"isAdmin": true
}

View file

@ -0,0 +1,44 @@
The solution is actually pretty simple.
Take a look at this:
```js
Promise.all(
fetch('https://api.github.com/users/iliakan'),
fetch('https://api.github.com/users/remy'),
fetch('http://no-such-url')
)
```
Here we have an array of `fetch(...)` promises that goes to `Promise.all`.
We can't change the way `Promise.all` works: if it detects an error, then it rejects with it. So we need to prevent any error from occurring. Instead, if a `fetch` error happens, we need to treat it as a "normal" result.
Here's how:
```js
Promise.all(
fetch('https://api.github.com/users/iliakan').catch(err => err),
fetch('https://api.github.com/users/remy').catch(err => err),
fetch('http://no-such-url').catch(err => err)
)
```
In other words, the `.catch` takes an error for all of the promises and returns it normally. By the rules of how promises work, if a `.then/catch` handler returns a value (doesn't matter if it's an error object or something else), then the execution continues the "normal" flow.
So the `.catch` returns the error as a "normal" result into the outer `Promise.all`.
This code:
```js
Promise.all(
urls.map(url => fetch(url))
)
```
Can be rewritten as:
```js
Promise.all(
urls.map(url => fetch(url).catch(err => err))
)
```

View file

@ -0,0 +1,24 @@
<!DOCTYPE HTML>
<html>
<body>
<script>
let urls = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'http://no-such-url'
];
Promise.all(
urls.map(url => fetch(url).catch(err => err))
)
.then(responses => {
alert(responses[0].status); // 200
alert(responses[1].status); // 200
alert(responses[2]); // TypeError: failed to fetch (text may vary)
});
</script>
</body>
</html>

View file

@ -0,0 +1,28 @@
<!DOCTYPE HTML>
<html>
<body>
<script>
// if any of URLs fails, other results are ignored
// change that:
// make errors appear as members of the responses array, together with normal results
let urls = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'http://no-such-url'
];
// Fix me:
Promise.all(urls.map(url => fetch(url)))
// Demo output (no need to change):
.then(responses => {
alert(responses[0].status); // 200
alert(responses[1].status); // 200
alert(responses[2]); // TypeError: failed to fetch (text may vary)
});
</script>
</body>
</html>

View file

@ -0,0 +1,48 @@
# Fault-tolerant Promise.all
We'd like to fetch multiple URLs in parallel.
Here's the code to do that:
```js run
let urls = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'https://api.github.com/users/jeresig'
];
Promise.all(urls.map(url => fetch(url)))
// for each response show its status
.then(responses => { // (*)
for(let response of responses) {
alert(`${response.url}: ${response.status}`);
}
});
```
The problem is that if any of requests fails, then `Promise.all` rejects with the error, and we lose results of all the other requests.
That's not good.
Modify the code so that the array `responses` in the line `(*)` would include the response objects for successful fetches and error objects for failed ones.
For instance, if one of URLs is bad, then it should be like:
```js
let urls = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'http://no-such-url'
];
Promise.all(...) // your code to fetch URLs...
// ...and pass fetch errors as members of the resulting array...
.then(responses => {
// 3 urls => 3 array members
alert(responses[0].status); // 200
alert(responses[1].status); // 200
alert(responses[2]); // TypeError: failed to fetch (text may vary)
});
```
P.S. In this task you don't have to load the full response using `response.text()` or `response.json()`. Just handle fetch errors the right way.

View file

@ -0,0 +1,29 @@
<!DOCTYPE HTML>
<html>
<body>
<script>
let urls = [
'https://api.github.com/users/iliakan',
'/',
'http://no-such-url'
];
Promise.all(
urls.map(url => fetch(url).catch(err => err))
)
.then(responses => Promise.all(
// if it's an error then pass on
// otherwise response.json() and catch errors as results
responses.map(r => r instanceof Error ? r : r.json().catch(err => err))
))
.then(results => {
alert(results[0].name); // Ilya Kantor
alert(results[1]); // SyntaxError: Unexpected token < in JSON at position 0
alert(results[2]); // TypeError: failed to fetch (text may vary)
});
</script>
</body>
</html>

View file

@ -0,0 +1,33 @@
<!DOCTYPE HTML>
<html>
<body>
<script>
// the whole promise chain fails with an error here
// change that:
// make errors appear as members of the results array
let urls = [
'https://api.github.com/users/iliakan',
// this URL is HTML page, it's invalid JSON, so response.json() fails
'/',
// this URL is invalid, so fetch fails
'http://no-such-url'
];
// Fix it:
Promise.all(urls.map(url => fetch(url)))
.then(responses => Promise.all(
responses.map(r => r.json())
))
// Demo output (no need to change):
.then(results => {
alert(results[0].name); // Ilya Kantor
alert(results[1]); // SyntaxError: Unexpected token < in JSON at position 0
alert(results[2]); // TypeError: failed to fetch (text may vary)
});
</script>
</body>
</html>

View file

@ -0,0 +1,34 @@
# Fault-tolerant fetch with JSON
Improve the solution of the previous task <info:task/promise-errors-as-results>. Now we need not just to call `fetch`, but to load the JSON objects from given URLs.
Here's the example code to do that:
```js run
let urls = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'https://api.github.com/users/jeresig'
];
// make fetch requests
Promise.all(urls.map(url => fetch(url)))
// map each response to response.json()
.then(responses => Promise.all(
responses.map(r => r.json())
))
// show name of each user
.then(users => { // (*)
for(let user of users) {
alert(user.name);
}
});
```
The problem is that if any of requests fails, then `Promise.all` rejects with the error, and we lose results of all the other requests. So the code above is not fault-tolerant, just like the one in the previous task.
Modify the code so that the array in the line `(*)` would include parsed JSON for successful requests and error for errored ones.
Please note that the error may occur both in `fetch` (if the network request fails) and in `response.json()` (if the response is invalid JSON). In both cases the error should become a member of the results object.
The sandbox has both of these cases.

View file

@ -0,0 +1,202 @@
# Promise API
There are 4 static methods in the `Promise` class. We'll quickly cover their use cases here.
## Promise.resolve
The syntax:
```js
let promise = Promise.resolve(value);
```
Returns a resolved promise with the given `value`.
Same as:
```js
let promise = new Promise(resolve => resolve(value));
```
The method is used when we already have a value, but would like to have it "wrapped" into a promise.
For instance, the `loadCached` function below fetches the `url` and remembers the result, so that future calls on the same URL return it immediately:
```js
function loadCached(url) {
let cache = loadCached.cache || (loadCached.cache = new Map());
if (cache.has(url)) {
*!*
return Promise.resolve(cache.get(url)); // (*)
*/!*
}
return fetch(url)
.then(response => response.text())
.then(text => {
cache.set(url,text);
return text;
});
}
```
We can use `loadCached(url).then(…)`, because the function is guaranteed to return a promise. That's the purpose `Promise.resolve` in the line `(*)`: it makes sure the interface unified. We can always use `.then` after `loadCached`.
## Promise.reject
The syntax:
```js
let promise = Promise.reject(error);
```
Create a rejected promise with the `error`.
Same as:
```js
let promise = new Promise((resolve, reject) => reject(error));
```
We cover it here for completeness, rarely used in real code.
## Promise.all
The method to run many promises in parallel and wait till all of them are ready.
The syntax is:
```js
let promise = Promise.all(iterable);
```
It takes an `iterable` object with promises, technically it can be any iterable, but usually it's an array, and returns a new promise. The new promise resolves with when all of them are settled and has an array of their results.
For instance, the `Promise.all` below settles after 3 seconds, and then its result is an array `[1, 2, 3]`:
```js run
Promise.all([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 3000)), // 1
new Promise((resolve, reject) => setTimeout(() => resolve(2), 2000)), // 2
new Promise((resolve, reject) => setTimeout(() => resolve(3), 1000)) // 3
]).then(alert); // 1,2,3 when promises are ready: each promise contributes an array member
```
Please note that the relative order is the same. Even though the first promise takes the longest time to resolve, it is still first in the array of results.
A common trick is to map an array of job data into an array of promises, and then wrap that into `Promise.all`.
For instance, if we have an array of URLs, we can fetch them all like this:
```js run
let urls = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'https://api.github.com/users/jeresig'
];
// map every url to the promise fetch(github url)
let requests = urls.map(url => fetch(url));
// Promise.all waits until all jobs are resolved
Promise.all(requests)
.then(responses => responses.forEach(
response => alert(`${response.url}: ${response.status}`)
));
```
A more real-life example with fetching user information for an array of github users by their names (or we could fetch an array of goods by their ids, the logic is same):
```js run
let names = ['iliakan', 'remy', 'jeresig'];
let requests = names.map(name => fetch(`https://api.github.com/users/${name}`));
Promise.all(requests)
.then(responses => {
// all responses are ready, we can show HTTP status codes
for(let response of responses) {
alert(`${response.url}: ${response.status}`); // shows 200 for every url
}
return responses;
})
// map array of responses into array of response.json() to read their content
.then(responses => Promise.all(responses.map(r => r.json())))
// all JSON answers are parsed: "users" is the array of them
.then(users => users.forEach(user => alert(user.name)));
```
If any of the promises is rejected, `Promise.all` immediately rejects with that error.
For instance:
```js run
Promise.all([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
*!*
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)),
*/!*
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).catch(alert); // Error: Whoops!
```
Here the second promise rejects in two seconds. That leads to immediate rejection of `Promise.all`, so `.catch` executes: the rejection error becomes the outcome of the whole `Promise.all`.
The important detail is that promises provide no way to "cancel" or "abort" their execution. So other promises continue to execute, and the eventually settle, but all their results are ignored.
There are ways to avoid this: we can either write additional code to `clearTimeout` (or otherwise cancel) the promises in case of an error, or we can make errors show up as members in the resulting array (see the task below this chapter about it).
````smart header="`Promise.all(iterable)` allows non-promise items in `iterable`"
Normally, `Promise.all(iterable)` accepts an iterable (in most cases an array) of promises. But if any of those objects is not a promise, it's wrapped in `Promise.resolve`.
For instance, here the results are `[1, 2, 3]`:
```js run
Promise.all([
new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 1000)
}),
2, // treated as Promise.resolve(2)
3 // treated as Promise.resolve(3)
]).then(alert); // 1, 2, 3
```
So we are able to pass non-promise values to `Promise.all` where convenient.
````
## Promise.race
Similar to `Promise.all` takes an iterable of promises, but instead of waiting for all of them to finish -- waits for the first result (or error), and goes on with it.
The syntax is:
```js
let promise = Promise.race(iterable);
```
For instance, here the result will be `1`:
```js run
Promise.race([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).then(alert); // 1
```
So, the first result/error becomes the result of the whole `Promise.race`. After the first settled promise "wins the race", all further results/errors are ignored.
## Summary
There are 4 static methods of `Promise` class:
1. `Promise.resolve(value)` -- makes a resolved promise with the given value,
2. `Promise.reject(error)` -- makes a rejected promise with the given error,
3. `Promise.all(promises)` -- waits for all promises to resolve and returns an array of their results. If any of the given promises rejects, then it becomes the error of `Promise.all`, and all other results are ignored.
4. `Promise.race(promises)` -- waits for the first promise to settle, and its result/error becomes the outcome.
Of these four, `Promise.all` is the most common in practice.

View file

@ -0,0 +1,13 @@
<script>
function loadScript(src) {
return new Promise(function(resolve, reject) {
let script = document.createElement('script');
script.src = src;
script.onload = () => resolve(script);
script.onerror = () => reject(new Error("Script load error: " + src));
document.head.append(script);
});
}
</script>

View file

@ -0,0 +1,4 @@
{
"name": "iliakan",
"isAdmin": true
}

View file

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

View file

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

View file

@ -0,0 +1,49 @@
There are no tricks here. Just replace `.catch` with `try...catch` inside `demoGithubUser` and add `async/await` where needed:
```js run
class HttpError extends Error {
constructor(response) {
super(`${response.status} for ${response.url}`);
this.name = 'HttpError';
this.response = response;
}
}
async function loadJson(url) {
let response = await fetch(url);
if (response.status == 200) {
return response.json();
} else {
throw new HttpError(response);
}
}
// Ask for a user name until github returns a valid user
async function demoGithubUser() {
let user;
while(true) {
let name = prompt("Enter a name?", "iliakan");
try {
user = await loadJson(`https://api.github.com/users/${name}`);
break; // no error, exit loop
} catch(err) {
if (err instanceof HttpError && err.response.status == 404) {
// loop continues after the alert
alert("No such user, please reenter.");
} else {
// unknown error, rethrow
throw err;
}
}
}
alert(`Full name: ${user.name}.`);
return user;
}
demoGithubUser();
```

View file

@ -0,0 +1,48 @@
# Rewrite "rethrow" async/await
Below you can find the "rethrow" example from the chapter <info:promise-chaining>. Rewrite it using `async/await` instead of `.then/catch`.
And get rid of the recursion in favour of a loop in `demoGithubUser`: with `async/await` that becomes easy to do.
```js run
class HttpError extends Error {
constructor(response) {
super(`${response.status} for ${response.url}`);
this.name = 'HttpError';
this.response = response;
}
}
function loadJson(url) {
return fetch(url)
.then(response => {
if (response.status == 200) {
return response.json();
} else {
throw new HttpError(response);
}
})
}
// Ask for a user name until github returns a valid user
function demoGithubUser() {
let name = prompt("Enter a name?", "iliakan");
return loadJson(`https://api.github.com/users/${name}`)
.then(user => {
alert(`Full name: ${user.name}.`);
return user;
})
.catch(err => {
if (err instanceof HttpError && err.response.status == 404) {
alert("No such user, please reenter.");
return demoGithubUser();
} else {
throw err;
}
});
}
demoGithubUser();
```

View file

@ -0,0 +1,33 @@
The notes are below the code:
```js run
async function loadJson(url) { // (1)
let response = await fetch(url); // (2)
if (response.status == 200) {
let json = await response.json(); // (3)
return json;
}
throw new Error(response.status);
}
loadJson('no-such-user.json')
.catch(alert); // Error: 404 (4)
```
Notes:
1. The function `loadUrl` becomes `async`.
2. All `.then` inside are replaced with `await`.
3. We can `return response.json()` instead of awaiting for it, like this:
```js
if (response.status == 200) {
return response.json(); // (3)
}
```
Then the outer code would have to `await` for that promise to resolve. In our case it doesn't matter.
4. The error thrown from `loadJson` is handled by `.catch`. We can't use `await loadJson(…)` there, because we're not in an `async` function.

View file

@ -0,0 +1,20 @@
# Rewrite using async/await
Rewrite the one of examples from the chapter <info:promise-chaining> using `async/await` instead of `.then/catch`:
```js run
function loadJson(url) {
return fetch(url)
.then(response => {
if (response.status == 200) {
return response.json();
} else {
throw new Error(response.status);
}
})
}
loadJson('no-such-user.json') // (3)
.catch(alert); // Error: 404
```

View file

@ -0,0 +1,298 @@
# Async/await
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 functions
Let's start with the `async` keyword. It can be placed before a function, like this:
```js
async function f() {
return 1;
}
```
The word "async" before a function means one simple thing: a function always returns a promise. Even If a function actually returns a non-promise value, prepending the function definition with the "async" keyword directs Javascript to automatically wrap that value in a resolved promise.
For instance, the code above returns a resolved promise with the result of `1`, let's test it:
```js run
async function f() {
return 1;
}
f().then(alert); // 1
```
...We could explicitly return a promise, that would be the same:
```js run
async function f() {
return Promise.resolve(1);
}
f().then(alert); // 1
```
So, `async` ensures that the function returns a promise, and wraps non-promises in it. Simple enough, right? But not only that. There's another keyword, `await`, that works only inside `async` functions, and it's pretty cool.
## Await
The syntax:
```js
// works only inside async functions
let value = await promise;
```
The keyword `await` makes JavaScript wait until that promise settles and returns its result.
Here's an example with a promise that resolves in 1 second:
```js run
async function f() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("done!"), 1000)
});
*!*
let result = await promise; // wait till the promise resolves (*)
*/!*
alert(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 makes JavaScript wait until the promise settles, and then go on with the result. That doesn't cost any CPU resources, because the engine can do other jobs meanwhile: 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.
````warn header="Can't use `await` in regular functions"
If we try to use `await` in non-async function, that would be a syntax error:
```js run
function f() {
let promise = Promise.resolve(1);
*!*
let result = await promise; // Syntax error
*/!*
}
```
We will get this error if we do not put `async` before a function. As said, `await` only works inside an `async function`.
````
Let's take the `showAvatar()` example from the chapter <info:promise-chaining> and rewrite it using `async/await`:
1. We'll need to replace `.then` calls with `await`.
2. Also we should make the function `async` for them to work.
```js run
async function showAvatar() {
// read our JSON
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();
// read github user
let githubResponse = await fetch(`https://api.github.com/users/${user.name}`);
let githubUser = await githubResponse.json();
// show the avatar
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
// wait 3 seconds
await new Promise((resolve, reject) => setTimeout(resolve, 3000));
img.remove();
return githubUser;
}
showAvatar();
```
Pretty clean and easy to read, right? Much better than before.
````smart header="`await` won't work in the top-level code"
People who are just starting to use `await` tend to forget the fact that we can't use `await` in top-level code. For example, this will not work:
```js run
// syntax error in top-level code
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();
```
So we need to have a wrapping async function for the code that awaits. Just as in the example above.
````
````smart header="`await` accepts thenables"
Like `promise.then`, `await` allows to use thenable objects (those with a callable `then` method). Again, the idea is that a 3rd-party object may not be a promise, but promise-compatible: if it supports `.then`, that's enough to use with `await`.
For instance, here `await` accepts `new Thenable(1)`:
```js run
class Thenable {
constructor(num) {
this.num = num;
}
then(resolve, reject) {
alert(resolve); // function() { native code }
// resolve with this.num*2 after 1000ms
setTimeout(() => resolve(this.num * 2), 1000); // (*)
}
};
async function f() {
// waits for 1 second, then result becomes 2
let result = await new Thenable(1);
alert(result);
}
f();
```
If `await` gets a non-promise object with `.then`, it calls that method providing native functions `resolve`, `reject` as arguments. Then `await` waits until one of them is called (in the example above it happens in the line `(*)`) and then proceeds with the result.
````
````smart header="Async methods"
A class method can also be async, just put `async` before it.
Like here:
```js run
class Waiter {
*!*
async wait() {
*/!*
return await Promise.resolve(1);
}
}
new Waiter()
.wait()
.then(alert); // 1
```
The meaning is the same: it ensures that the returned value is a promise and enables `await`.
````
## Error handling
If a promise resolves normally, then `await promise` returns the result. But in case of a rejection, it throws the error, just as if there were a `throw` statement at that line.
This code:
```js
async function f() {
*!*
await Promise.reject(new Error("Whoops!"));
*/!*
}
```
...Is the same as this:
```js
async function f() {
*!*
throw new Error("Whoops!");
*/!*
}
```
In real situations, the promise may take some time before it rejects. So `await` will wait, and then throw an error.
We can catch that error using `try..catch`, the same way as a regular `throw`:
```js run
async function f() {
try {
let response = await fetch('http://no-such-url');
} catch(err) {
*!*
alert(err); // TypeError: failed to fetch
*/!*
}
}
f();
```
In case of an error, the control jumps to the `catch` block. We can also wrap multiple lines:
```js run
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();
```
If we don't have `try..catch`, then the promise generated by the call of the async function `f()` becomes rejected. We can append `.catch` to handle it:
```js run
async function f() {
let response = await fetch('http://no-such-url');
}
// f() becomes a rejected promise
*!*
f().catch(alert); // TypeError: failed to fetch // (*)
*/!*
```
If we forget to add `.catch` there, then we get an unhandled promise error (and can see it in the console). We can catch such errors using a global event handler as described in the chapter <info:promise-chaining>.
```smart header="`async/await` and `promise.then/catch`"
When we use `async/await`, we rarely need `.then`, because `await` handles the waiting for us. And we can use a regular `try..catch` instead of `.catch`. That's usually (not always) more convenient.
But at the top level of the code, when we're outside of any `async` function, we're syntactically unable to use `await`, so it's a normal practice to add `.then/catch` to handle the final result or falling-through errors.
Like in the line `(*)` of the example above.
```
````smart header="`async/await` works well with `Promise.all`"
When we need to wait for multiple promises, we can wrap them in `Promise.all` and then `await`:
```js
// wait for the array of results
let results = await Promise.all([
fetch(url1),
fetch(url2),
...
]);
```
In case of an error, it propagates as usual: from the failed promise to `Promise.all`, and then becomes an exception that we can catch using `try..catch` around the call.
````
## Summary
The `async` keyword before a function has two effects:
1. Makes it always return a promise.
2. Allows to use `await` in it.
The `await` keyword before a promise makes JavaScript wait until that promise settles, and then:
1. If it's an error, the exception is generated, same as if `throw error` were called at that very place.
2. Otherwise, it returns the result, so we can assign it to a value.
Together they provide a great framework to write asynchronous code that is easy both to read and write.
With `async/await` we rarely need to write `promise.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 a nice thing to wait for many tasks simultaneously.

View file

@ -0,0 +1,8 @@
<style>
.promise-avatar-example {
border-radius: 50%;
position: fixed;
left: 10px;
top: 10px;
}
</style>

2
1-js/09-async/index.md Normal file
View file

@ -0,0 +1,2 @@
# Promises, async/await

View file

@ -1,15 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<script type="module">
import weekdays from './weekdays.js';
alert(weekdays[0]);
</script>
</body>
</html>

View file

@ -1 +0,0 @@
export default ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];

View file

@ -1,70 +0,0 @@
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports, __webpack_require__) {
'use strict';
var _nums = __webpack_require__(1);
document.write('Сумма импортов: ' + (_nums.one + _nums.two));
/***/ },
/* 1 */
/***/ function(module, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var one = 1;
exports.one = one;
var two = 2;
exports.two = two;
/***/ }
/******/ ]);

View file

@ -1,11 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<script src="bundle.js"></script>
</body>
</html>

View file

@ -1,3 +0,0 @@
import {one, two} from './nums';
document.write(`Сумма импортов: ${one + two}`);

View file

@ -1,5 +0,0 @@
export let one = 1;
let two = 2;
export {two};

View file

@ -1,22 +0,0 @@
// Для использования нужен Node.JS
// Поставьте webpack:
// npm i -g webpack
// Поставьте babel-loader:
// npm i babel-loader
// Запустите его в директории с файлами:
// webpack
module.exports = {
entry: './main',
output: {
filename: 'bundle.js'
},
module: {
loaders: [
{ test: /\.js$/, loader: "babel" }
]
}
};

View file

@ -0,0 +1,413 @@
# Export and Import
Export and import directives are very versatile.
In the previous chapter we saw a simple use, now let's explore more examples.
## Export before declarations
We can label any declaration as exported by placing `export` before it, be it a variable, function or a class.
For instance, here all exports are valid:
```js
// export an array
*!*export*/!* let months = ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
// export a constant
*!*export*/!* const MODULES_BECAME_STANDARD_YEAR = 2015;
// export a class
*!*export*/!* class User {
constructor(name) {
this.name = name;
}
}
```
````smart header="No semicolons after export class/function"
Please note that `export` before a class or a function does not make it a [function expression](info:function-expressions-arrows). It's still a function declaration, albeit exported.
Most Javascript style guides recommend semicolons after statements, but not after function and class declarations.
That's why there should bes no semicolons at the end of `export class` and `export function`.
```js
export function sayHi(user) {
alert(`Hello, ${user}!`);
} *!* // no ; at the end */!*
```
````
## Export after declarations
Also, we can put `export` separately.
Here we first declare, and then export:
```js
// 📁 say.js
function sayHi(user) {
alert(`Hello, ${user}!`);
}
function sayBye(user) {
alert(`Bye, ${user}!`);
}
*!*
export {sayHi, sayBye}; // a list of exported variables
*/!*
```
...Or, technically we could put `export` above functions as well.
## Import *
Usually, we put a list of what to import into `import {...}`, like this:
```js
// 📁 main.js
*!*
import {sayHi, sayBye} from './say.js';
*/!*
sayHi('John'); // Hello, John!
sayBye('John'); // Bye, John!
```
But if the list is long, we can import everything as an object using `import * as <obj>`, for instance:
```js
// 📁 main.js
*!*
import * as say from './say.js';
*/!*
say.sayHi('John');
say.sayBye('John');
```
At first sight, "import everything" seems such a cool thing, short to write, why should we ever explicitly list what we need to import?
Well, there are few reasons.
1. Modern build tools ([webpack](http://webpack.github.io) and others) bundle modules together and optimize them to speedup loading and remove unused stuff.
Let's say, we added a 3rd-party library `lib.js` to our project with many functions:
```js
// 📁 lib.js
export function sayHi() { ... }
export function sayBye() { ... }
export function becomeSilent() { ... }
```
Now if we in fact need only one of them in our project:
```js
// 📁 main.js
import {sayHi} from './lib.js';
```
...Then the optimizer will automatically detect it and totally remove the other functions from the bundled code, thus making the build smaller. That is called "tree-shaking".
2. Explicitly listing what to import gives shorter names: `sayHi()` instead of `lib.sayHi()`.
3. Explicit imports give better overview of the code structure: what is used and where. It makes code support and refactoring easier.
## Import "as"
We can also use `as` to import under different names.
For instance, let's import `sayHi` into the local variable `hi` for brevity, and same for `sayBye`:
```js
// 📁 main.js
*!*
import {sayHi as hi, sayBye as bye} from './say.js';
*/!*
hi('John'); // Hello, John!
bye('John'); // Bye, John!
```
## Export "as"
The similar syntax exists for `export`.
Let's export functions as `hi` and `bye`:
```js
// 📁 say.js
...
export {sayHi as hi, sayBye as bye};
```
Now `hi` and `bye` are official names for outsiders:
```js
// 📁 main.js
import * as say from './say.js';
say.hi('John'); // Hello, John!
say.bye('John'); // Bye, John!
```
## export default
So far, we've seen how to import/export multiple things, optionally "as" other names.
In practice, modules contain either:
- A library, pack of functions, like `lib.js`.
- Or an entity, like `class User` is descirbed in `user.js`, the whole module has only this class.
Mostly, the second approach is preferred, so that every "thing" resides in its own module.
Naturally, that requires a lot of files, as everything wants its own module, but that's not a problem at all. Actually, code navigation becomes easier, if files are well-named and structured into folders.
Modules provide special `export default` syntax to make "one thing per module" way look better.
It requires following `export` and `import` statements:
1. Put `export default` before the "main export" of the module.
2. Call `import` without curly braces.
For instance, here `user.js` exports `class User`:
```js
// 📁 user.js
export *!*default*/!* class User { // just add "default"
constructor(name) {
this.name = name;
}
}
```
...And `main.js` imports it:
```js
// 📁 main.js
import *!*User*/!* from './user.js'; // not {User}, just User
new User('John');
```
Imports without curly braces look nicer. A common mistake when starting to use modules is to forget curly braces at all. So, remember, `import` needs curly braces for named imports and doesn't need them for the default one.
| Named export | Default export |
|--------------|----------------|
| `export class User {...}` | `export default class User {...}` |
| `import {User} from ...` | `import User from ...`|
Naturally, there may be only one "default" export per file.
We may have both default and named exports in a single module, but in practice people usually don't mix them. A module has either named exports or the default one.
**Another thing to note is that named exports must (naturally) have a name, while `export default` may be anonymous.**
For instance, these are all perfecly valid default exports:
```js
export default class { // no class name
constructor() { ... }
}
export default function(user) { // no function name
alert(`Hello, ${user}!`);
}
// export a single value, without making a variable
export default ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
```
That's fine, because `export default` is only one per file, so `import` always knows what to import.
Contrary to that, omitting a name for named imports would be an error:
```js
export class { // Error! (non-default export needs a name)
constructor() {}
}
```
When importing, to keep the code consistent, teams usually employ the rule that local variables should be named after files, e.g:
```js
import User from './user.js';
import LoginForm from './loginForm.js';
import func from '/path/to/func.js';
...
```
### "Default" alias
The "default" word is a kind of "alias" for the default export, for scenarios when we need to reference it somehow.
For example, if we already have a function declared, that's how to `export default` it:
```js
function sayHi(user) {
alert(`Hello, ${user}!`);
}
export {sayHi as default}; // same as if we added "export default" before the function
```
Or, let's say a module `user.js` exports one main "default" thing and a few named ones (rarely the case, but happens):
```js
// 📁 user.js
export default class User {
constructor(name) {
this.name = name;
}
}
export function sayHi(user) {
alert(`Hello, ${user}!`);
}
```
Here's how to import the default export along with a named one:
```js
// 📁 main.js
import {*!*default as User*/!*, sayHi} from './user.js';
new User('John');
```
Or, if we consider importing `*` as an object, then the `default` property is exactly the default export:
```js
// 📁 main.js
import * as user from './user.js';
let User = user.default;
new User('John');
```
## Re-export
"Re-export" syntax `export ... from ...` allows to import things and immediately export them (possibly under another name), like this:
```js
export {sayHi} from './say.js';
export {default as User} from './user.js';
```
What's the point, why that's needed? Let's see a practical use case.
Imagine, we're writing a "package": a folder with a lot of modules, mostly needed internally, with some of the functionality exported outside (tools like NPM allow to publish and distribute packages, but here it doesn't matter).
A directory structure could be like this:
```
auth/
index.js
user.js
helpers.js
tests/
login.js
providers/
github.js
facebook.js
...
```
We'd like to expose the package functionality via a single entry point, the "main file" `auth/index.js`, to be used like this:
```js
import {login, logout} from 'auth/index.js'
```
The idea is that outsiders, developers who use our package, should not meddle with its internal structure. They should not search for files inside our package folder. We export only what's necessary in `auth/index.js` and keep the rest hidden from prying eyes.
Now, as the actual exported functionality is scattered among the package, we can gather and "re-export" it in `auth/index.js`:
```js
// 📁 auth/index.js
import {login, logout} from './helpers.js';
export {login, logout};
import User from './user.js';
export {User};
import Githib from './providers/github.js';
export {Github};
...
```
"Re-exporting" is just a shorter notation for that:
```js
// 📁 auth/index.js
export {login, logout} from './helpers.js';
// or, to re-export all helpers, we could use:
// export * from './helpers.js';
export {default as User} from './user.js';
export {default as Githib} from './providers/github.js';
...
```
````warn header="Re-exporting default is tricky"
Please note: `export User from './user.js'` won't work. It's actually a syntax error. To re-export the default export, we must mention it explicitly `{default as ...}`, like in the example above.
Also, there's another oddity: `export * from './user.js'` re-exports only named exports, exluding the default one. Once again, we need to mention it explicitly.
For instance, to re-export everything, two statements will be necessary:
```js
export * from './module.js'; // to re-export named exports
export {default} from './module.js'; // to re-export default
```
The default should be mentioned explicitly only when re-exporting: `import * as obj` works fine. It imports the default export as `obj.default`. So there's a slight asymmetry between import and export constructs here.
````
## Summary
There are following types of `export`:
- Before declaration:
- `export [default] class/function/variable ...`
- Standalone:
- `export {x [as y], ...}`.
- Re-export:
- `export {x [as y], ...} from "mod"`
- `export * from "mod"` (doesn't re-export default).
- `export {default [as y]} from "mod"` (re-export default).
Import:
- Named exports from module:
- `import {x [as y], ...} from "mod"`
- Default export:
- `import x from "mod"`
- `import {default as x} from "mod"`
- Everything:
- `import * as obj from "mod"`
- Only fetch/evalute the module, don't import:
- `import "mod"`
We can put import/export statements below or after other code, that doesn't matter.
So this is technically fine:
```js
sayHi();
import {sayHi} from './say.js'; // import at the end of the file
```
In practice imports are usually at the start of the file, but that's only for better convenience.
**Please note that all import/export statements must be at the top level of the script.**
This won't work:
```js
if (something) {
import {sayHi} from "./say.js"; // Error: import must be at top level
}
```
...But what if we really need to import something dynamically? Like, load a module upon request, when it's really needed? We'll see how to do it in the next chapter.

View file

@ -1,704 +0,0 @@
# Modules
As our application grows bigger, we want to split it into multiple files, so called 'modules'.
A module usually contains a class or a library of useful functions. Then, for browsers we bundle them together with a special tool such as [webpack](https://webpack.js.org/) and deploy to the production server.
For a long time, Javascript existed without a language-level module syntax. That wasn't a problem, because initially scripts were small and simple. So there was no need.
But eventually scripts became more and more complex, so the community invented a variety of ways to organize code into modules.
For instance:
- [AMD](https://en.wikipedia.org/wiki/Asynchronous_module_definition) -- one of the most ancient module systems, initially implemented by the library [require.js](http://requirejs.org/).
- [CommonJS](http://wiki.commonjs.org/wiki/Modules/1.1) -- the module system created for Node.JS server.
- [UMD](https://github.com/umdjs/umd) -- one more module system, suggested as a universal one, compatible with AMD and CommonJS.
Now all these slowly become a part of history, but we still can find them in old scripts.
The language-level module system appeared in the standard in 2015, gradually evolved since then, and is now supported by all major browsers and in Node.js.
## What is a module?
A module is just a file. One single script, as simple as that.
Directives `export` and `import` allow to interchange functionality between modules:
- `export` keyword labels variables and functions that should be accessible from outside the file.
- `import` allows to import functionality from other modules.
For instance, if we have a file `sayHi.js` exporting a function:
```js
// 📁 sayHi.js
export function sayHi(user) {
alert(`Hello, ${user}!`);
}
```
...Then another file may import and use it:
```js
// 📁 main.js
import {sayHi} from './sayHi.js';
alert(sayHi); // function...
sayHi('John'); // Hello, John!
```
So, one Javascript file ("module") may import functionality from other ones. Not any functionality, but only what's intended (exported).
**Each module has its own top-level scope.**
In other words, global variables from one file are only seen in that file, they do not get into other files. If we want something to be available for other modules -- let's export it.
For browser scripts, that's something new, so modules require a special tag `<script type="module">` to function properly. In the next chapter we'll see that in more detail.
**A module code is evaluated only the first time when imported.**
Let's see what it means on examples.
First, if executing a module has side-effects, like showing a message, and we import it multiple times -- from one or many other modules, the message is printed only once:
```js
// 📁 alert.js
alert("Module is evaluated!");
```
```js
// 📁 1.js
import ... from `./alert.js`; // Module is evaluated!
// 📁 2.js
import ... from `./alert.js`; // (nothing)
```
So, top-level module code is mostly used for initialization. We create data structures, pre-fill them, and if we want something to be reusable -- export it.
Another example. Let's say, a module exports an object:
```js
// 📁 admin.js
export let admin = {
name: "John",
password: "*****"
};
```
Now, if this module is imported from multiple files, the module is only evaluated the first time, `admin` object is created, and then passed to all further importers.
All importers get exactly the one and only `admin` object, with all modifications in it:
```js
// 📁 1.js
import {admin} from './admin.js';
admin.name = "Pete";
// 📁 2.js
import {admin} from './admin.js';
alert(admin.name); // Pete
*!*
// Both 1.js and 2.js imported the same object
// Changes made in 1.js are visible in 2.js
*/!*
```
This feature is quite essential to modules that require configuration. We can set required properties on the first import, and then in further imports it's already ready.
For instance, `admin` module may export additional functionality that uses the object:
```js
// 📁 admin.js
export let admin = { ... };
export function sayHi() {
alert(`Ready to serve, ${admin.name}!`);
}
```
Now if we set `admin.name` once in `init.js`, everyone will see it, including calls made from inside `admin.js` itself:
```js
// 📁 init.js
import {admin} from './admin.js';
admin.name = "Pete";
```
```js
// 📁 other.js (after init.js)
import {admin, sayHi} from './admin.js';
alert(admin.name); // *!*Pete*/!*
sayHi(); // Ready to serve, *!*Pete*/!*!
```
To summarize, the core concepts are:
1. A module is a file. To make a file treated properly, browsers need `<script type="module">`, we'll cover that later.
2. Modules have their own, local top-level scope and interchange functionality via `import/export`.
3. Module code is executed only once. Exports are created once and shared between importers.
## Export and Import
We can label any declaration as exported by placing `export` before it, be it a variable, function or a class.
For instance, here all exports are valid:
```js
// export an array
*!*export*/!* let months = ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
// export a constant
*!*export*/!* const MODULES_BECAME_STANDARD_YEAR = 2015;
// export a class
*!*export*/!* class User {
constructor(name) {
this.name = name;
}
}
```
````smart header="No semicolons after export class/function"
Please note that `export` before a class or a function does not make it a [function expression](info:function-expressions-arrows). It's still a function declaration, albeit exported.
Most Javascript style guides recommend semicolons after statements, but not after function and class declarations.
That's why there should bes no semicolons at the end of `export class` and `export function`.
```js
export function sayHi(user) {
alert(`Hello, ${user}!`);
} *!* // no ; at the end */!*
```
````
### export after declarations
Also, we can put `export` separately.
Here we first declare, and then export:
```js
// 📁 say.js
function sayHi(user) {
alert(`Hello, ${user}!`);
}
function sayBye(user) {
alert(`Bye, ${user}!`);
}
*!*
export {sayHi, sayBye}; // a list of exported variables
*/!*
```
### import *
Usually, we put a list of what to import into `import {...}`, like this:
```js
// 📁 main.js
*!*
import {sayHi, sayBye} from './say.js';
*/!*
sayHi('John'); // Hello, John!
sayBye('John'); // Bye, John!
```
But if the list is long, we can import everything as an object using `import * as <obj>`, for instance:
```js
// 📁 main.js
*!*
import * as say from './say.js';
*/!*
say.sayHi('John');
say.sayBye('John');
```
At first sight, "import everything" seems such a cool thing, short to write, why should we ever explicitly list what we need to import?
Well, there are few reasons.
1. Modern build tools ([webpack](http://webpack.github.io) and others) bundle modules together and optimize them to speedup loading and remove unused stuff.
Let's say, we added a 3rd-party library `lib.js` to our project with many functions:
```js
// 📁 lib.js
export function sayHi() { ... }
export function sayBye() { ... }
export function becomeSilent() { ... }
```
Now if we in fact need only one of them in our project:
```js
// 📁 main.js
import {sayHi} from './lib.js';
```
...Then the optimizer will automatically detect it and totally remove the other functions from the bundled code, thus making the build smaller. That is called "tree-shaking".
2. Explicitly listing what to import gives shorter names: `sayHi()` instead of `lib.sayHi()`.
3. Explicit imports give better overview of the code structure: what is used and where. It makes code support and refactoring easier.
## import "as"
We can also use `as` to import under different names.
For instance, let's import `sayHi` into the local variable `hi` for brevity, and same for `sayBye`:
```js
// 📁 main.js
*!*
import {sayHi as hi, sayBye as bye} from './say.js';
*/!*
hi('John'); // Hello, John!
bye('John'); // Bye, John!
```
## export "as"
The similar syntax exists for `export`.
Let's export functions as `hi` and `bye`:
```js
// 📁 say.js
...
export {sayHi as hi, sayBye as bye};
```
Now `hi` and `bye` are official names for outsiders:
```js
// 📁 main.js
import * as say from './say.js';
say.hi('John'); // Hello, John!
say.bye('John'); // Bye, John!
```
## Named and default exports
So far, we've seen how to import/export multiple things, optionally "as" other names.
In practice, modules contain either:
- A library, pack of functions, like `lib.js`.
- Or an entity, like `class User` is descirbed in `user.js`, the whole module has only this class.
Mostly, the second approach is preferred, so that every 'thing' resides in its own module.
Naturally, that requires a lot of files, as everything wants its own module, but that's not a problem at all. Actually, code navigation becomes easier, if files are well-named and structured into folders.
Modules provide special `export default` syntax to make "one thing per module" way look better.
It requires following `export` and `import` statements:
1. Put `export default` before the "main export" of the module.
2. Call `import` without curly braces.
For instance, here `user.js` exports `class User`:
```js
// 📁 user.js
export *!*default*/!* class User { // just add "default"
constructor(name) {
this.name = name;
}
}
```
...And `main.js` imports it:
```js
// 📁 main.js
import *!*User*/!* from './user.js'; // not {User}, just User
new User('John');
```
Imports without curly braces look nicer. A common mistake when starting to use modules is to forget curly braces at all. So, remember, `import` needs curly braces for named imports and doesn't need them for the default one.
| Named export | Default export |
|--------------|----------------|
| `export class User {...}` | `export default class User {...}` |
| `import {User} from ...` | `import User from ...`|
Naturally, there may be only one "default" export per file.
We may have both default and named exports in a single module, but in practice people usually don't mix them. A module has either named exports or the default one.
**Another thing to note is that named exports must (naturally) have a name, while `export default` may be anonymous.**
For instance, these are all perfecly valid default exports:
```js
export default class { // no class name
constructor() { ... }
}
export default function(user) { // no function name
alert(`Hello, ${user}!`);
}
// export a single value, without making a variable
export default ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
```
That's fine, because `export default` is only one per file, so `import` always knows what to import.
Contrary to that, omitting a name for named imports would be an error:
```js
export class { // Error! (non-default export needs a name)
constructor() {}
}
```
When importing, to keep the code consistent, teams usually employ the rule that local variables should be named after files, e.g:
```js
import User from './user.js';
import LoginForm from './loginForm.js';
import func from '/path/to/func.js';
...
```
## The "default" alias
The "default" word is a kind of "alias" for the default export, for scenarios when we need to reference it somehow.
For example, if we already have a function declared, that's how to `export default` it:
```js
function sayHi(user) {
alert(`Hello, ${user}!`);
}
export {sayHi as default}; // same as if we added "export default" before the function
```
Or, imagine a file `user.js` exporting one main "default" thing and a few named ones (rarely the case, but happens):
```js
// 📁 user.js
export default class User {
constructor(name) {
this.name = name;
}
}
export function sayHi(user) {
alert(`Hello, ${user}!`);
}
```
Here's how to import the default export along with a named one:
```js
// 📁 main.js
import {*!*default as User*/!*, sayHi} from './user.js';
new User('John');
```
Or, if we consider importing `*` as an object, then the `default` property is exactly the default export:
```js
// 📁 main.js
import * as user from './user.js';
let User = user.default;
new User('John');
```
## Re-export
"Re-export" syntax `export ... from ...` allows to import things and immediately export them (possibly under another name), like this:
```js
export {sayHi} from './say.js';
export {default as User} from './user.js';
```
What's the point, why that's needed? Let's see a practical use case.
Imagine, we're writing a "package": a folder with a lot of modules, mostly needed internally, with some of the functionality exported outside (tools like NPM allow to publish and distribute packages, but here it doesn't matter).
A directory structure could be like this:
```
auth/
index.js
user.js
helpers.js
tests/
login.js
providers/
github.js
facebook.js
...
```
We'd like to expose the package functionality via a single entry point, the "main file" `auth/index.js`, to be used like this:
```js
import {login, logout} from 'auth/index.js'
```
The idea is that outsiders, developers who use our package, should not meddle with its internal structure. They should not search for files inside our package folder. We export only what's necessary in `auth/index.js` and keep the rest hidden from prying eyes.
Now, as the actual exported functionality is scattered among the package, we can gather and "re-export" it in `auth/index.js`:
```js
// 📁 auth/index.js
import {login, logout} from './helpers.js';
export {login, logout};
import User from './user.js';
export {User};
import Githib from './providers/github.js';
export {Github};
...
```
"Re-exporting" is just a shorter notation for that:
```js
// 📁 auth/index.js
export {login, logout} from './helpers.js';
// or, to re-export all helpers, we could use:
// export * from './helpers.js';
export {default as User} from './user.js';
export {default as Githib} from './providers/github.js';
...
```
````warn header="Re-exporting default is tricky"
Please note: `export User from './user.js'` won't work. It's actually a syntax error.
To re-export the default export, we must mention it explicitly `{default as ...}`, like in the example above.
Also, there's another oddity: `export * from './user.js'` re-exports only named exports, exluding the default one. Once again, we need to mention it explicitly.
For instance, to re-export everything, two statements will be necessary:
```js
export * from './module.js'; // to re-export named exports
export {default} from './module.js'; // to re-export default
```
The issue arises only when re-exporting: `import * as obj` works fine, it imports the default export as `obj.default`.
````
## Summary
There are following types of `export`:
- Before declaration: `export [default] class/function/variable ...`
- Standalone: `export {x [as y]}`.
- Re-export: `export {x [as y]} from ...`, `export * from ...`.
There
export var v; null null "v" "v"
export default function f(){}; null null "f" "default"
export default function(){}; null null "*default*" "default"
export default 42; null null "*default*" "default"
export {x}; null null "x" "x"
export {x as v}; null null "x" "v"
export {x} from "mod"; "mod" "x" null "x"
export {x as v} from "mod"; "mod" "x" null "v"
export * from "mod"; "mod" "*" null null
## Modules in the browser
In this tutorial we concentrate on the language itself, but now let's see some demos, and we're going to use browser modules for that. So let's see how to use modules in the browser. You may want to skip this section if you have no idea about browser Javascript, and still be able to understand the rest.
To use modules in the browser, in `<script>` we must set the attribute `type='module'`, like this:
```html
<script type='module'>
import {sayHi, sayBye} from './say.js';
sayHi('John'); // Hello, John!
sayBye('John'); // Bye, John!
</script>
```
Scripts with `type='module'` have several important differences:
1. They always have enabled `use strict`. So e.g. assigning to undeclared variables will give an error:
```html
<script type='module'>
a = 5; // error
</script>
```
2. Scripts are always deferred, same effect as `defer` attribute (described in the chapter [](info:onload-ondomcontentloaded)), for both external and inline scripts. Or, to put it short, they always run after the HTML is fully downloaded and rendered.
```html run
<script type='module'>
alert(button); // this script can 'see' elements below
// as modules are deferred, they runs after the whole page is loaded
</script>
<script>
alert(button); // Error: button is undefined,
// the script runs immediately, before the rest of the page can be seen
// that's regular behavior for inline scripts, 'defer' attribute is ignored on them
</script>
<button id='button'>Button</button>
```
3. Async attribute `<script async type='module'>` is allowed on both inline and external scripts. Async scripts run immediately when and imported modules are processed, independantly of other scripts or the HTML document.
3. External scripts with same `src` run only once:
```html
<!-- the script my.js is fetched and executed only once -->
<script type='module' src='my.js'></script>
<script type='module' src='my.js'></script>
```
4. External scripts that are fetched from another domain require [CORS](mdn:Web/HTTP/CORS) headers. In other words, if a module script is fetched from another domain, the remote server must supply a header `Access-Control-Allow-Origin: *` (may use fetching domain instead of `*`) to indicate that the fetch is allowed.
```html
<!-- the server another-site.com must supply Access-Control-Allow-Origin -->
<!-- otherwise, the script won't execute -->
<script type='module' src='*!*http://another-site.com*/!*/their.js'></script>
```
5. And, probably, the most notable difference: modules have their own global scope. In other words, top-level variables and functions from modules are not seen in other scripts.
```html
<script type='module'>
let user = 'John';
</script>
<script type='module'>
*!*
alert(user); // Error: user is not defined
*/!*
</script>
```
That's natural: modules are expected to `export` what they want to be accessible from outside. And interact via export/import. If we really need the shared `window` scope, then we can use `window.user`, but that should be an exception requiring a good reason.
If you look at the list, all items are a 'good thing', leading to correct coding practices.
## export default
As we've just seen, a module can export individual variables.
But it's quite common for a file to implement a single thing. For instance, a file `user.js` describes `class User`, a file `login.js` -- `function login()` for authorization, and so on. One file -- one entity.
There's a special `export default` statement to better implement that approch. Only one may exist per module, and it labels the export as 'the default one', that may be imported directly without figure brackets.
For instance, `user.js`:
```js
*!*export default*/!* class User {
constructor(name) {
this.name = name;
}
};
```
...And in `login.js`:
```js
import User from './user';
new User('John');
```
The default export is technically just a syntax sugar. We can live without it.
If `user.js` had the regular `export` without `default`, then `login.js` would need to import with figure brackets:
```js
// if user.js had
// export class User { ... }
// …then to import it we'd need figuree brackets:
import {User} from './user';
new User('John');
```
In practice, such 'sugar' is very convenient, it allows to easily see what exactly is exported and omit extra characters while importing.
Please note that default exports may be unnamed:
```js
// that's allowed:
export default function() {}
```
We can also export a regular value, like an array with days of week:
```js
// weekdays.js
export default ['Monday', 'Tuesday', ..., 'Sunday'];
```
That's fine, as the default export is only one per module, so `import` knows what to get:
```js
// assuming export default is used
import weekdays from './weekdays';
alert(weekdays[0]); // Monday
```
Usually, there's a rule that the default export is imported under the name of the module itself, or under the name of the corresponding entity. Like `import weekdays from './weekdays'` or `import User from './user'`.
## Использование
Современный стандарт ECMAScript описывает, как импортировать и экспортировать значения из модулей, но он ничего не говорит о том, как эти модули искать, загружать и т.п.
Такие механизмы предлагались в процессе создания стандарта, но были убраны по причине недостаточной проработанности. Возможно, они появятся в будущем.
Сейчас используются системы сборки, как правило, в сочетании с Babel.JS.
Система сборки обрабатывает скрипты, находит в них `import/export` и заменяет их на свои внутренние JavaScript-вызовы. При этом, как правило, много файлов-модулей объединяются в один или несколько скриптов, смотря как указано в конфигурации сборки.
Ниже вы можете увидеть полный пример использования модулей с системой сборки [webpack](http://webpack.github.io).
В нём есть:
- `nums.js` -- модуль, экспортирующий `one` и `two`, как описано выше.
- `main.js` -- модуль, который импортирует `one`, `two` из `nums` и выводит их сумму.
- `webpack.config.js` -- конфигурация для системы сборки.
- `bundle.js` -- файл, который создала система сборки из `main.js` и `nums.js`.
- `index.html` -- простой HTML-файл для демонстрации.
[codetabs src='nums']
## Итого
Современный стандарт описывает, как организовать код в модули, экспортировать и импортировать значения.
Экспорт:
- `export` можно поставить прямо перед объявлением функции, класса, переменной.
- Если `export` стоит отдельно от объявления, то значения в нём указываются в фигурных скобках: `export {…}`.
- Также можно экспортировать 'значение по умолчанию' при помощи `export default`.
Импорт:
- В фигурных скобках указываются значения, а затем -- модуль, откуда их брать: `import {a, b, c as d} from 'module'`.
- Можно импортировать все значения в виде объекта при помощи `import * as obj from 'module'`.
- Без фигурных скобок будет импортировано 'значение по умолчанию': `import User from 'user'`.
На текущий момент модули требуют системы сборки на сервере. Автор этого текста преимущественно использует webpack, но есть и другие варианты.

View file

@ -1,15 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<script type="module">
import weekdays from './weekdays.js';
alert(weekdays[0]);
</script>
</body>
</html>

View file

@ -1 +0,0 @@
export default ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];

View file

@ -1,70 +0,0 @@
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports, __webpack_require__) {
'use strict';
var _nums = __webpack_require__(1);
document.write('Сумма импортов: ' + (_nums.one + _nums.two));
/***/ },
/* 1 */
/***/ function(module, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var one = 1;
exports.one = one;
var two = 2;
exports.two = two;
/***/ }
/******/ ]);

View file

@ -1,11 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<script src="bundle.js"></script>
</body>
</html>

View file

@ -1,3 +0,0 @@
import {one, two} from './nums';
document.write(`Сумма импортов: ${one + two}`);

View file

@ -1,5 +0,0 @@
export let one = 1;
let two = 2;
export {two};

View file

@ -1,22 +0,0 @@
// Для использования нужен Node.JS
// Поставьте webpack:
// npm i -g webpack
// Поставьте babel-loader:
// npm i babel-loader
// Запустите его в директории с файлами:
// webpack
module.exports = {
entry: './main',
output: {
filename: 'bundle.js'
},
module: {
loaders: [
{ test: /\.js$/, loader: "babel" }
]
}
};

View file

@ -1,13 +0,0 @@
<!doctype html>
<html>
<body>
<script type="module">
import {sayHi, sayBye} from './say.js';
sayHi('John'); // Hello, John!
sayBye('John'); // Bye, John!
</script>
</body>
</html>

View file

@ -1,7 +0,0 @@
export function sayHi(user) {
alert(`Hello, ${user}!`);
}
export function sayBye(user) {
alert(`Bye, ${user}!`);
}

View file

@ -0,0 +1,48 @@
# Dynamic imports
Export and import statements that we covered in previous chaters are called "static".
What's because they are indeed static.
First, we can't generate module name. It must be a string. This won't work:
```js
import {func} from getModuleName(); // Error, only from "string" is allowed
```
Second, we can't import conditionally or at run-time:
```js
if(...) {
import ...; // Error, not allowed!
}
{
import ...; // Error, we can't put import in a block, must be at the top level
}
```
So, import/export provide a rigid code structure. That's a good thing, as code structure can be analyzed, modules can be gathered and bundled together, unused exports can be removed (tree-shaken), just because everything is fixed.
But how do we import a module on-demand?
## The import() function
The `import(module)` function can be called from anywhere. It returns a promise that resolved into a module object.
The usage pattern looks like this:
```js run
let modulePath = prompt("Module path?");
import(modulePath)
.then(obj => <module object>)
.catch(err => <loading error, no such module?>)
```
Or, we could use `async/away` here, like this:
[codetabs src="say" current="index.html"]
Please note: dynamic imports work in regular scripts, they don't require `script type="module"`, like static imports.

View file

@ -0,0 +1,10 @@
<!doctype html>
<button onclick="load()">Click me</button>
<script>
async function load() {
let say = await import('./say.js');
say.hi(); // Hello!
say.bye(); // Bye!
say.default(); // Module loaded (export default)!
}
</script>

View file

@ -0,0 +1,11 @@
export function hi() {
alert(`Hello`);
}
export function bye() {
alert(`Bye`);
}
export default function() {
alert("Module loaded (export default)!");
}

View file

@ -1,693 +0,0 @@
# Modules
As our application grows bigger, we want to split it into multiple files, so called 'modules'.
A module usually contains a class or a library of useful functions. Then, for browsers we bundle them together with a special tool such as [webpack](https://webpack.js.org/) and deploy to the production server.
For a long time, Javascript existed without a language-level module syntax. That wasn't a problem, because initially scripts were small and simple. So there was no need.
But eventually scripts became more and more complex, so the community invented a variety of ways to organize code into modules.
For instance:
- [AMD](https://en.wikipedia.org/wiki/Asynchronous_module_definition) -- one of the most ancient module systems, initially implemented by the library [require.js](http://requirejs.org/).
- [CommonJS](http://wiki.commonjs.org/wiki/Modules/1.1) -- the module system created for Node.JS server.
- [UMD](https://github.com/umdjs/umd) -- one more module system, suggested as a universal one, compatible with AMD and CommonJS.
Now all these slowly become a part of history, but we still can find them in old scripts.
The language-level module system appeared in the standard in 2015, gradually evolved since then, and is now supported by all major browsers and in Node.js.
## What is a module?
A module is just a file. One single script, as simple as that.
Directives `export` and `import` allow to interchange functionality between modules:
- `export` keyword labels variables and functions that should be accessible from outside the file.
- `import` allows to import functionality from other modules.
For instance, if we have a file `sayHi.js` exporting a function:
```js
// 📁 sayHi.js
export function sayHi(user) {
alert(`Hello, ${user}!`);
}
```
...Then another file may import and use it:
```js
// 📁 main.js
import {sayHi} from './sayHi.js';
alert(sayHi); // function...
sayHi('John'); // Hello, John!
```
So, one Javascript file ("module") may import functionality from other ones. Not any functionality, but only what's intended (exported).
**Each module has its own top-level scope.**
In other words, global variables from one file are only seen in that file, they do not get into other files. If we want something to be available for other modules -- let's export it.
For browser scripts, that's something new, so modules require a special tag `<script type="module">` to function properly. In the next chapter we'll see that in more detail.
**A module code is evaluated only the first time when imported.**
Let's see what it means on examples.
First, if executing a module has side-effects, like showing a message, and we import it multiple times -- from one or many other modules, the message is printed only once:
```js
// 📁 alert.js
alert("Module is evaluated!");
```
```js
// 📁 1.js
import ... from `./alert.js`; // Module is evaluated!
// 📁 2.js
import ... from `./alert.js`; // (nothing)
```
So, top-level module code is mostly used for initialization. We create data structures, pre-fill them, and if we want something to be reusable -- export it.
Another example. Let's say, a module exports an object:
```js
// 📁 admin.js
export let admin = {
name: "John",
password: "*****"
};
```
Now, if this module is imported from multiple files, the module is only evaluated the first time, `admin` object is created, and then passed to all further importers.
All importers get exactly the one and only `admin` object, with all modifications in it:
```js
// 📁 1.js
import {admin} from './admin.js';
admin.name = "Pete";
// 📁 2.js
import {admin} from './admin.js';
alert(admin.name); // Pete
*!*
// Both 1.js and 2.js imported the same object
// Changes made in 1.js are visible in 2.js
*/!*
```
This feature is quite essential to modules that require configuration. We can set required properties on the first import, and then in further imports it's already ready.
For instance, `admin` module may export additional functionality that uses the object:
```js
// 📁 admin.js
export let admin = { ... };
export function sayHi() {
alert(`Ready to serve, ${admin.name}!`);
}
```
Now if we set `admin.name` once in `init.js`, everyone will see it, including calls made from inside `admin.js` itself:
```js
// 📁 init.js
import {admin} from './admin.js';
admin.name = "Pete";
```
```js
// 📁 other.js (after init.js)
import {admin, sayHi} from './admin.js';
alert(admin.name); // *!*Pete*/!*
sayHi(); // Ready to serve, *!*Pete*/!*!
```
To summarize, the core concepts are:
1. A module is a file. To make a file treated properly, browsers need `<script type="module">`, we'll cover that later.
2. Modules have their own, local top-level scope and interchange functionality via `import/export`.
3. Module code is executed only once. Exports are created once and shared between importers.
## Export and Import
We can label any declaration as exported by placing `export` before it, be it a variable, function or a class.
For instance, here all exports are valid:
```js
// export an array
*!*export*/!* let months = ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
// export a constant
*!*export*/!* const MODULES_BECAME_STANDARD_YEAR = 2015;
// export a class
*!*export*/!* class User {
constructor(name) {
this.name = name;
}
}
```
````smart header="No semicolons after export class/function"
Please note that `export` before a class or a function does not make it a [function expression](info:function-expressions-arrows). It's still a function declaration, albeit exported.
Most Javascript style guides recommend semicolons after statements, but not after function and class declarations.
That's why there should bes no semicolons at the end of `export class` and `export function`.
```js
export function sayHi(user) {
alert(`Hello, ${user}!`);
} *!* // no ; at the end */!*
```
````
### export after declarations
Also, we can put `export` separately.
Here we first declare, and then export:
```js
// 📁 say.js
function sayHi(user) {
alert(`Hello, ${user}!`);
}
function sayBye(user) {
alert(`Bye, ${user}!`);
}
*!*
export {sayHi, sayBye}; // a list of exported variables
*/!*
```
### import *
Usually, we put a list of what to import into `import {...}`, like this:
```js
// 📁 main.js
*!*
import {sayHi, sayBye} from './say.js';
*/!*
sayHi('John'); // Hello, John!
sayBye('John'); // Bye, John!
```
But if the list is long, we can import everything as an object using `import * as <obj>`, for instance:
```js
// 📁 main.js
*!*
import * as say from './say.js';
*/!*
say.sayHi('John');
say.sayBye('John');
```
At first sight, "import everything" seems such a cool thing, short to write, why should we ever explicitly list what we need to import?
Well, there are few reasons.
1. Modern build tools ([webpack](http://webpack.github.io) and others) bundle modules together and optimize them to speedup loading and remove unused stuff.
Let's say, we added a 3rd-party library `lib.js` to our project with many functions:
```js
// 📁 lib.js
export function sayHi() { ... }
export function sayBye() { ... }
export function becomeSilent() { ... }
```
Now if we in fact need only one of them in our project:
```js
// 📁 main.js
import {sayHi} from './lib.js';
```
...Then the optimizer will automatically detect it and totally remove the other functions from the bundled code, thus making the build smaller. That is called "tree-shaking".
2. Explicitly listing what to import gives shorter names: `sayHi()` instead of `lib.sayHi()`.
3. Explicit imports give better overview of the code structure: what is used and where. It makes code support and refactoring easier.
## import "as"
We can also use `as` to import under different names.
For instance, let's import `sayHi` into the local variable `hi` for brevity, and same for `sayBye`:
```js
// 📁 main.js
*!*
import {sayHi as hi, sayBye as bye} from './say.js';
*/!*
hi('John'); // Hello, John!
bye('John'); // Bye, John!
```
## export "as"
The similar syntax exists for `export`.
Let's export functions as `hi` and `bye`:
```js
// 📁 say.js
...
export {sayHi as hi, sayBye as bye};
```
Now `hi` and `bye` are official names for outsiders:
```js
// 📁 main.js
import * as say from './say.js';
say.hi('John'); // Hello, John!
say.bye('John'); // Bye, John!
```
## Named and default exports
So far, we've seen how to import/export multiple things, optionally "as" other names.
In practice, modules contain either:
- A library, pack of functions, like `lib.js`.
- Or an entity, like `class User` is descirbed in `user.js`, the whole module has only this class.
Mostly, the second approach is preferred, so that every 'thing' resides in its own module.
Naturally, that requires a lot of files, as everything wants its own module, but that's not a problem at all. Actually, code navigation becomes easier, if files are well-named and structured into folders.
Modules provide special `export default` syntax to make "one thing per module" way look better.
It requires following `export` and `import` statements:
1. Put `export default` before the "main export" of the module.
2. Call `import` without curly braces.
For instance, here `user.js` exports `class User`:
```js
// 📁 user.js
export *!*default*/!* class User { // just add "default"
constructor(name) {
this.name = name;
}
}
```
...And `main.js` imports it:
```js
// 📁 main.js
import *!*User*/!* from './user.js'; // not {User}, just User
new User('John');
```
Imports without curly braces look nicer. A common mistake when starting to use modules is to forget curly braces at all. So, remember, `import` needs curly braces for named imports and doesn't need them for the default one.
| Named export | Default export |
|--------------|----------------|
| `export class User {...}` | `export default class User {...}` |
| `import {User} from ...` | `import User from ...`|
Naturally, there may be only one "default" export per file.
We may have both default and named exports in a single module, but in practice people usually don't mix them. A module has either named exports or the default one.
**Another thing to note is that named exports must (naturally) have a name, while `export default` may be anonymous.**
For instance, these are all perfecly valid default exports:
```js
export default class { // no class name
constructor() { ... }
}
export default function(user) { // no function name
alert(`Hello, ${user}!`);
}
// export a single value, without making a variable
export default ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
```
That's fine, because `export default` is only one per file, so `import` always knows what to import.
Contrary to that, omitting a name for named imports would be an error:
```js
export class { // Error! (non-default export needs a name)
constructor() {}
}
```
When importing, to keep the code consistent, teams usually employ the rule that local variables should be named after files, e.g:
```js
import User from './user.js';
import LoginForm from './loginForm.js';
import func from '/path/to/func.js';
...
```
## The "default" alias
The "default" word is a kind of "alias" for the default export, for scenarios when we need to reference it somehow.
For example, if we already have a function declared, that's how to `export default` it:
```js
function sayHi(user) {
alert(`Hello, ${user}!`);
}
export {sayHi as default}; // same as if we added "export default" before the function
```
Or, imagine a file `user.js` exporting one main "default" thing and a few named ones (rarely the case, but happens):
```js
// 📁 user.js
export default class User {
constructor(name) {
this.name = name;
}
}
export function sayHi(user) {
alert(`Hello, ${user}!`);
}
```
Here's how to import the default export along with a named one:
```js
// 📁 main.js
import {*!*default as User*/!*, sayHi} from './user.js';
new User('John');
```
Or, if we consider importing `*` as an object, then the `default` property is exactly the default export:
```js
// 📁 main.js
import * as user from './user.js';
let User = user.default;
new User('John');
```
## Re-export
"Re-export" syntax `export ... from ...` allows to import things and immediately export them (possibly under another name), like this:
```js
export {sayHi} from './say.js';
export {default as User} from './user.js';
```
What's the point, why that's needed? Let's see a practical use case.
Imagine, we're writing a "package": a folder with a lot of modules, mostly needed internally, with some of the functionality exported outside (tools like NPM allow to publish and distribute packages, but here it doesn't matter).
A directory structure could be like this:
```
auth/
index.js
user.js
helpers.js
tests/
login.js
providers/
github.js
facebook.js
...
```
We'd like to expose the package functionality via a single entry point, the "main file" `auth/index.js`, to be used like this:
```js
import {login, logout} from 'auth/index.js'
```
The idea is that outsiders, developers who use our package, should not meddle with its internal structure. They should not search for files inside our package folder. We export only what's necessary in `auth/index.js` and keep the rest hidden from prying eyes.
Now, as the actual exported functionality is scattered among the package, we can gather and "re-export" it in `auth/index.js`:
```js
// 📁 auth/index.js
import {login, logout} from './helpers.js';
export {login, logout};
import User from './user.js';
export {User};
import Githib from './providers/github.js';
export {Github};
...
```
"Re-exporting" is just a shorter notation for that:
```js
// 📁 auth/index.js
export {login, logout} from './helpers.js';
// or, to re-export all helpers, we could use:
// export * from './helpers.js';
export {default as User} from './user.js';
export {default as Githib} from './providers/github.js';
...
```
````warn header="Re-exporting default is tricky"
Please note: `export User from './user.js'` won't work. It's actually a syntax error.
To re-export the default export, we must mention it explicitly `{default as ...}`, like in the example above.
Also, there's another oddity: `export * from './user.js'` re-exports only named exports, exluding the default one. Once again, we need to mention it explicitly.
For instance, to re-export everything, two statements will be necessary:
```js
export * from './module.js'; // to re-export named exports
export {default} from './module.js'; // to re-export default
```
The issue arises only when re-exporting: `import * as obj` works fine, it imports the default export as `obj.default`.
````
## Summary
There are following types of `export`:
- Before declaration: `export [default] class/function/variable ...`
- Standalone: `export {x [as y]}`.
- Re-export: `export {x [as y]} from ...`, `export * from ...`.
There
export var v; null null "v" "v"
export default function f(){}; null null "f" "default"
export default function(){}; null null "*default*" "default"
export default 42; null null "*default*" "default"
export {x}; null null "x" "x"
export {x as v}; null null "x" "v"
export {x} from "mod"; "mod" "x" null "x"
export {x as v} from "mod"; "mod" "x" null "v"
export * from "mod"; "mod" "*" null null
## Modules in the browser
In this tutorial we concentrate on the language itself, but now let's see some demos, and we're going to use browser modules for that. So let's see how to use modules in the browser. You may want to skip this section if you have no idea about browser Javascript, and still be able to understand the rest.
To use modules in the browser, in `<script>` we must set the attribute `type='module'`, like this:
```html
<script type='module'>
import {sayHi, sayBye} from './say.js';
sayHi('John'); // Hello, John!
sayBye('John'); // Bye, John!
</script>
```
Scripts with `type='module'` have several important differences:
1. They always have enabled `use strict`. So e.g. assigning to undeclared variables will give an error:
```html
<script type='module'>
a = 5; // error
</script>
```
2. Scripts are always deferred, same effect as `defer` attribute (described in the chapter [](info:onload-ondomcontentloaded)), for both external and inline scripts. Or, to put it short, they always run after the HTML is fully downloaded and rendered.
```html run
<script type='module'>
alert(button); // this script can 'see' elements below
// as modules are deferred, they runs after the whole page is loaded
</script>
<script>
alert(button); // Error: button is undefined,
// the script runs immediately, before the rest of the page can be seen
// that's regular behavior for inline scripts, 'defer' attribute is ignored on them
</script>
<button id='button'>Button</button>
```
3. Async attribute `<script async type='module'>` is allowed on both inline and external scripts. Async scripts run immediately when and imported modules are processed, independantly of other scripts or the HTML document.
3. External scripts with same `src` run only once:
```html
<!-- the script my.js is fetched and executed only once -->
<script type='module' src='my.js'></script>
<script type='module' src='my.js'></script>
```
4. External scripts that are fetched from another domain require [CORS](mdn:Web/HTTP/CORS) headers. In other words, if a module script is fetched from another domain, the remote server must supply a header `Access-Control-Allow-Origin: *` (may use fetching domain instead of `*`) to indicate that the fetch is allowed.
```html
<!-- the server another-site.com must supply Access-Control-Allow-Origin -->
<!-- otherwise, the script won't execute -->
<script type='module' src='*!*http://another-site.com*/!*/their.js'></script>
```
5. And, probably, the most notable difference: modules have their own global scope. In other words, top-level variables and functions from modules are not seen in other scripts.
```html
<script type='module'>
let user = 'John';
</script>
<script type='module'>
*!*
alert(user); // Error: user is not defined
*/!*
</script>
```
That's natural: modules are expected to `export` what they want to be accessible from outside. And interact via export/import. If we really need the shared `window` scope, then we can use `window.user`, but that should be an exception requiring a good reason.
If you look at the list, all items are a 'good thing', leading to correct coding practices.
## export default
As we've just seen, a module can export individual variables.
But it's quite common for a file to implement a single thing. For instance, a file `user.js` describes `class User`, a file `login.js` -- `function login()` for authorization, and so on. One file -- one entity.
There's a special `export default` statement to better implement that approch. Only one may exist per module, and it labels the export as 'the default one', that may be imported directly without figure brackets.
For instance, `user.js`:
```js
*!*export default*/!* class User {
constructor(name) {
this.name = name;
}
};
```
...And in `login.js`:
```js
import User from './user';
new User('John');
```
The default export is technically just a syntax sugar. We can live without it.
If `user.js` had the regular `export` without `default`, then `login.js` would need to import with figure brackets:
```js
// if user.js had
// export class User { ... }
// …then to import it we'd need figuree brackets:
import {User} from './user';
new User('John');
```
In practice, such 'sugar' is very convenient, it allows to easily see what exactly is exported and omit extra characters while importing.
Please note that default exports may be unnamed:
```js
// that's allowed:
export default function() {}
```
We can also export a regular value, like an array with days of week:
```js
// weekdays.js
export default ['Monday', 'Tuesday', ..., 'Sunday'];
```
That's fine, as the default export is only one per module, so `import` knows what to get:
```js
// assuming export default is used
import weekdays from './weekdays';
alert(weekdays[0]); // Monday
```
Usually, there's a rule that the default export is imported under the name of the module itself, or under the name of the corresponding entity. Like `import weekdays from './weekdays'` or `import User from './user'`.
## Использование
Современный стандарт ECMAScript описывает, как импортировать и экспортировать значения из модулей, но он ничего не говорит о том, как эти модули искать, загружать и т.п.
Такие механизмы предлагались в процессе создания стандарта, но были убраны по причине недостаточной проработанности. Возможно, они появятся в будущем.
Сейчас используются системы сборки, как правило, в сочетании с Babel.JS.
Система сборки обрабатывает скрипты, находит в них `import/export` и заменяет их на свои внутренние JavaScript-вызовы. При этом, как правило, много файлов-модулей объединяются в один или несколько скриптов, смотря как указано в конфигурации сборки.
Ниже вы можете увидеть полный пример использования модулей с системой сборки [webpack](http://webpack.github.io).
В нём есть:
- `nums.js` -- модуль, экспортирующий `one` и `two`, как описано выше.
- `main.js` -- модуль, который импортирует `one`, `two` из `nums` и выводит их сумму.
- `webpack.config.js` -- конфигурация для системы сборки.
- `bundle.js` -- файл, который создала система сборки из `main.js` и `nums.js`.
- `index.html` -- простой HTML-файл для демонстрации.
[codetabs src='nums']
## Итого
Современный стандарт описывает, как организовать код в модули, экспортировать и импортировать значения.
Экспорт:
- `export` можно поставить прямо перед объявлением функции, класса, переменной.
- Если `export` стоит отдельно от объявления, то значения в нём указываются в фигурных скобках: `export {…}`.
- Также можно экспортировать 'значение по умолчанию' при помощи `export default`.
Импорт:
- В фигурных скобках указываются значения, а затем -- модуль, откуда их брать: `import {a, b, c as d} from 'module'`.
- Можно импортировать все значения в виде объекта при помощи `import * as obj from 'module'`.
- Без фигурных скобок будет импортировано 'значение по умолчанию': `import User from 'user'`.
На текущий момент модули требуют системы сборки на сервере. Автор этого текста преимущественно использует webpack, но есть и другие варианты.

View file

@ -1,15 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<script type="module">
import weekdays from './weekdays.js';
alert(weekdays[0]);
</script>
</body>
</html>

View file

@ -1 +0,0 @@
export default ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];

View file

@ -1,70 +0,0 @@
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports, __webpack_require__) {
'use strict';
var _nums = __webpack_require__(1);
document.write('Сумма импортов: ' + (_nums.one + _nums.two));
/***/ },
/* 1 */
/***/ function(module, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var one = 1;
exports.one = one;
var two = 2;
exports.two = two;
/***/ }
/******/ ]);

View file

@ -1,11 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<script src="bundle.js"></script>
</body>
</html>

View file

@ -1,3 +0,0 @@
import {one, two} from './nums';
document.write(`Сумма импортов: ${one + two}`);

View file

@ -1,5 +0,0 @@
export let one = 1;
let two = 2;
export {two};

View file

@ -1,22 +0,0 @@
// Для использования нужен Node.JS
// Поставьте webpack:
// npm i -g webpack
// Поставьте babel-loader:
// npm i babel-loader
// Запустите его в директории с файлами:
// webpack
module.exports = {
entry: './main',
output: {
filename: 'bundle.js'
},
module: {
loaders: [
{ test: /\.js$/, loader: "babel" }
]
}
};

View file

@ -1,13 +0,0 @@
<!doctype html>
<html>
<body>
<script type="module">
import {sayHi, sayBye} from './say.js';
sayHi('John'); // Hello, John!
sayBye('John'); // Bye, John!
</script>
</body>
</html>

View file

@ -1,7 +0,0 @@
export function sayHi(user) {
alert(`Hello, ${user}!`);
}
export function sayBye(user) {
alert(`Bye, ${user}!`);
}

View file

@ -1,4 +0,0 @@
# Dynos
...test

View file

@ -1,3 +1,2 @@
# Modules
s