up
This commit is contained in:
parent
e8db0f9c9f
commit
cce95cb631
13 changed files with 16955 additions and 50 deletions
9
8-async/02-promise-basics/02-delay-promise/solution.md
Normal file
9
8-async/02-promise-basics/02-delay-promise/solution.md
Normal 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.
|
14
8-async/02-promise-basics/02-delay-promise/task.md
Normal file
14
8-async/02-promise-basics/02-delay-promise/task.md
Normal 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'));
|
||||||
|
```
|
|
@ -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>
|
15
8-async/02-promise-basics/03-animate-circle-promise/task.md
Normal file
15
8-async/02-promise-basics/03-animate-circle-promise/task.md
Normal 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.
|
16767
8-async/03-promise-chaining/1.html
Normal file
16767
8-async/03-promise-chaining/1.html
Normal file
File diff suppressed because it is too large
Load diff
|
@ -116,6 +116,31 @@ Here each `.then` returns `new Promise(…)`. JavaScript awaits for it to settle
|
||||||
|
|
||||||
So the output is again 1 -> 2 > 4, but with 1 second delay between `alert` calls.
|
So the output is again 1 -> 2 > 4, but with 1 second delay between `alert` calls.
|
||||||
|
|
||||||
|
````smart header="Thenables"
|
||||||
|
To be precise, any object that has a method `.then` is treated as a promise here. So that we could use a custom "promise-compatible" object in the chain. Such objects are called "thenable".
|
||||||
|
|
||||||
|
Here's an example:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let thenable = {
|
||||||
|
then(resolve, reject) {
|
||||||
|
setTimeout(() => resolve(result * 2), 1000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
new Promise(resolve => resolve(1))
|
||||||
|
.then(result => {
|
||||||
|
return thenable;
|
||||||
|
})
|
||||||
|
.then(alert); // shows 2 after 1000ms
|
||||||
|
```
|
||||||
|
In practice we rarely need thenables, because if we want to make our own promise-compatible objects, then we can inherit from the native `Promise` class.
|
||||||
|
|
||||||
|
But that's still good, because gives us additional flexibility. We don't *have* to inherit from `Promise`. In particular, that allows to use a custom implementations of promises coming from 3rd-party libraries along with native promises.
|
||||||
|
````
|
||||||
|
|
||||||
|
## Example: loadScript
|
||||||
|
|
||||||
Let's use it with `loadScript` to load scripts one by one, in sequence:
|
Let's use it with `loadScript` to load scripts one by one, in sequence:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
|
@ -127,41 +152,27 @@ loadScript("/article/promise-chaining/one.js")
|
||||||
return loadScript("/article/promise-chaining/three.js");
|
return loadScript("/article/promise-chaining/three.js");
|
||||||
})
|
})
|
||||||
.then(function(script) {
|
.then(function(script) {
|
||||||
// use variables declared in scripts
|
// use functions declared in scripts
|
||||||
// to show that they indeed loaded
|
// to show that they indeed loaded
|
||||||
alert("Done: " + (one + two + three));
|
one();
|
||||||
|
two();
|
||||||
|
three();
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Here each `loadScript` call returns a promise, and the next `.then` awaits until it resolves. So scripts are loaded one after another.
|
||||||
|
|
||||||
We can add more asynchronous actions to the chain, and the code is still "flat", no "pyramid of doom".
|
We can add more asynchronous actions to the chain, and the code is still "flat", no signs of "pyramid of doom".
|
||||||
|
|
||||||
## Error handling
|
## Error handling
|
||||||
|
|
||||||
In case of an error, the closest `onRejected` handler down the chain is called.
|
Asynchronous actions do not always succeed. For instance, `loadScript` fails if there's no such script.
|
||||||
|
|
||||||
Let's recall that a rejection (error) handler may be assigned with two syntaxes:
|
Luckily, chaining is great for handling errors. When a promise rejects, the control jumps to the closest rejection handler down the chain.
|
||||||
|
|
||||||
- `.then(...,onRejected)`, as a second argument of `.then`.
|
In the example below we append `.catch` to handle all errors in the scripts loading chain:
|
||||||
- `.catch(onRejected)`, a shorthand for `.then(null, onRejected)`.
|
|
||||||
|
|
||||||
In the example below we use the second syntax to catch all errors in the script load chain:
|
|
||||||
|
|
||||||
```js run
|
```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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
*!*
|
*!*
|
||||||
loadScript("/article/promise-chaining/ERROR.js")
|
loadScript("/article/promise-chaining/ERROR.js")
|
||||||
*/!*
|
*/!*
|
||||||
|
@ -172,20 +183,18 @@ loadScript("/article/promise-chaining/ERROR.js")
|
||||||
return loadScript("/article/promise-chaining/three.js");
|
return loadScript("/article/promise-chaining/three.js");
|
||||||
})
|
})
|
||||||
.then(function(script) {
|
.then(function(script) {
|
||||||
// use variables declared in scripts
|
alert('done!');
|
||||||
// to show that they indeed loaded
|
|
||||||
alert("Done: " + (one + two + three));
|
|
||||||
})
|
})
|
||||||
*!*
|
*!*
|
||||||
.catch(function(error) { // (**)
|
.catch(function(error) { // (*)
|
||||||
alert(error.message);
|
alert(error.message);
|
||||||
});
|
});
|
||||||
*/!*
|
*/!*
|
||||||
```
|
```
|
||||||
|
|
||||||
In the code above the first `loadScript` call fails, because `ERROR.js` doesn't exist. The initial error is generated in the line `(*)`, then the first error handler in the chain is called, that is `(**)`.
|
In the code above the first `loadScript` call fails, because `ERROR.js` doesn't exist. The control jumps to the closest error handler `(*)`.
|
||||||
|
|
||||||
Now the same thing, but the error occurs in the second script:
|
The same error handler is called if an error occurs in the second script:
|
||||||
|
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
|
@ -199,9 +208,7 @@ loadScript("/article/promise-chaining/one.js")
|
||||||
return loadScript("/article/promise-chaining/three.js");
|
return loadScript("/article/promise-chaining/three.js");
|
||||||
})
|
})
|
||||||
.then(function(script) {
|
.then(function(script) {
|
||||||
// use variables declared in scripts
|
alert('done!');
|
||||||
// to show that they indeed loaded
|
|
||||||
alert("Done: " + (one + two + three));
|
|
||||||
})
|
})
|
||||||
*!*
|
*!*
|
||||||
.catch(function(error) {
|
.catch(function(error) {
|
||||||
|
@ -210,12 +217,14 @@ loadScript("/article/promise-chaining/one.js")
|
||||||
*/!*
|
*/!*
|
||||||
```
|
```
|
||||||
|
|
||||||
Once again, the `.catch` handles it.
|
The `.catch` works like in a `try..catch` block. 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.
|
||||||
|
|
||||||
|
|
||||||
### Implicit try..catch
|
## Implicit try..catch
|
||||||
|
|
||||||
Throwing an exception is considered a rejection.
|
Promises automatically take care about errors thrown inside them.
|
||||||
|
|
||||||
|
That's like an executor and handlers have invisible `try..catch` around them. If an error happens, it's considered a rejection.
|
||||||
|
|
||||||
For instance, this code:
|
For instance, this code:
|
||||||
|
|
||||||
|
@ -229,7 +238,7 @@ new Promise(function(resolve, reject) {
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
...Works the same way as:
|
...Works the same way as this:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
new Promise(function(resolve, reject) {
|
new Promise(function(resolve, reject) {
|
||||||
|
@ -241,35 +250,57 @@ new Promise(function(resolve, reject) {
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
Like there's an invisible `try..catch` around the whole code of the function, that catches errors.
|
The `Promise` constructor automatically catches the error and treats it as a rejection.
|
||||||
|
|
||||||
That works not only in the executor, but in handlers as well, for instance:
|
That works 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
|
```js run
|
||||||
new Promise(function(resolve, reject) {
|
new Promise(function(resolve, reject) {
|
||||||
resolve("ok")
|
resolve("ok");
|
||||||
}).then(function(result) {
|
}).then(function(result) {
|
||||||
*!*
|
*!*
|
||||||
|
// .then returns a rejected promise
|
||||||
throw new Error("Whoops!");
|
throw new Error("Whoops!");
|
||||||
*/!*
|
*/!*
|
||||||
})
|
}).catch(function(error) {
|
||||||
.catch(function(error) {
|
// and the error is handled here
|
||||||
alert(error.message); // Whoops!
|
alert(error.message); // 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(function(error) {
|
||||||
|
alert(error.message); // blabla is not defined
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
To summarize, a `.then` handler can finish in three ways:
|
||||||
|
|
||||||
|
1. It can return a value (or undefined if there's no `return`). Then the promise returned by `.then` becomes fulfilled, and the next handler is called with that value.
|
||||||
|
2. It can throw an error. Then the promise returned by `.then` becomes rejected and the closest rejection handler is called.
|
||||||
|
3. It can return a promise. Then JavaScript awaits its result and goes on with it.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## Rethrowing
|
## Rethrowing
|
||||||
|
|
||||||
As we already noticed, `.catch` is like `try..catch`. We may have as many `.then` as we want, and then use a single `.catch` at the end to handle errors in all of them.
|
As we already noticed, `.catch` 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 can't handle. The same thing is possible for promises.
|
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.
|
||||||
|
|
||||||
A handler in `.catch` can finish in two ways:
|
An error handler has the same 3 outcomes as described above. So if we `throw` inside `.catch`, then the control goes to the next closest error handler. And if we finish normally, then it continues to TODO
|
||||||
|
|
||||||
1. It can return a value or don't return anything. Then the execution continues "normally", the next `.then(onResolved)` handler is called.
|
|
||||||
2. It can throw an error. Then the execution goes the "error" path, and the closest rejection handler is called.
|
|
||||||
|
|
||||||
|
TODO TODO
|
||||||
Here is an example of the first behavior (the error is handled):
|
Here is an example of the first behavior (the error is handled):
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
|
|
BIN
8-async/03-promise-chaining/promise-handler-variants.png
Normal file
BIN
8-async/03-promise-chaining/promise-handler-variants.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
BIN
8-async/03-promise-chaining/promise-handler-variants@2x.png
Normal file
BIN
8-async/03-promise-chaining/promise-handler-variants@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 64 KiB |
|
@ -1 +1,3 @@
|
||||||
let three = 3;
|
function three() {
|
||||||
|
alert(3);
|
||||||
|
}
|
||||||
|
|
|
@ -1 +1,3 @@
|
||||||
let two = 2;
|
function two() {
|
||||||
|
alert(2);
|
||||||
|
}
|
||||||
|
|
4
8-async/03-promise-chaining/user.json
Normal file
4
8-async/03-promise-chaining/user.json
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"name": "iliakan",
|
||||||
|
"isAdmin": true
|
||||||
|
}
|
BIN
figures.sketch
BIN
figures.sketch
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue