# Promises
- Before discussing promises and why we need it, first take a look at this simple four-step calculation:
- start with
x = 5
- step 1: multiply
x
by2
(x = 10
) - step 2: add
6
tox
(x = 16
) - step 3: subtract
x
by1
(x = 15
) - step 4: output
x
to thepre
tag in the DOM (<pre>x = 15</pre>
)
- start with
- Open es6/promises/calculator_synchronous.html and es6/promises/calculator_synchronous.js
HTML
JavaScript
result
<pre></pre>
Copied!
1
- Let's do the same calculation, but this time:
- stap 1 has a delay of two seconds
- stap 2 has a delay of one second
- Open es6/promises/calculator_asynchronous.html and es6/promises/calculator_asynchronous.js
JavaScript
result
let x = 5; console.log(`x = ${x}`); // step 1 console.warn('Give me two seconds, first empty my coffee and then I will multiply x by 2'); setTimeout(() => { x *= 2; console.log(`x * 2 = ${x}`); }, 2000); // step 2 console.warn("Give me a sec, I'm coming down and add 6 to x"); setTimeout(() => { x += 6; console.log(`x + 6 = ${x}`); }, 1000); // step 3 x -= 1; console.log(`x - 1 = ${x}`); // step 4 (output x to DOM) document.querySelector('pre').innerText = `x = ${x}`;
Copied!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Synchronous vs Asynchronous functions
- To explain the behavior of the previous example, we have to know the difference between synchronous and asynchronous functions in JavaScript
- Javascript is a single threaded language:
- This means it has only one call stack
- A line of code must finish executing before it's moving to the next line of code
- This is called synchronous
- Most of the functions we already used are synchronous (e.g.
console.log()
,forEach()
,while()
, ...) - Some function in JavaScript can't give us a result immediately, gives us a result in time (e.g.
fetch()
,setTimeout()
,setInterval()
, DOM events, ...) - These functions are called asynchronous function and are treated differently
- When a synchronous function is recognizes, this functions is taken out of the main thread, so the execution don't block, and the next line of code is executed
- When the synchronous function is resolved, and when the main thread is finished, the result is sent back to the main thread
- Asynchronous function are non-Blocking
- Now the result of our previous example make sense:
original code
remove async code
after one second
after another second
result
let x = 5; console.log(`x = ${x}`); // step 1 console.warn('Give me two seconds, first empty my coffee and then I will multiply x by 2'); setTimeout(() => { x *= 2; console.log(`x * 2 = ${x}`); }, 2000); // step 2 console.warn("Give me a sec, I'm coming down and add 6 to x"); setTimeout(() => { x += 6; console.log(`x + 6 = ${x}`); }, 1000); // step 3 x -= 1; console.log(`x - 1 = ${x}`); // step 4 (output x to DOM) document.querySelector('pre').innerText = `x = ${x}`;
Copied!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
TIP
If you don't fully understand our (minimised) explanation of what async functions are, you can watch these movies and links with a more detailed explanation
# Callback (hell)
- What we really want is that the second
setTimeout()
function will be executed after the fitstsetTimeout()
and the last steps must be executed after the secondsetTimeout()
function - With the knowledge we have so far, there is only one way to resolve our problem: nested callback function
- Open es6/promises/calculator_callback.html and es6/promises/calculator_callback.js
- As you can see, we have a lot of nested functions inside each other
- This code is hard to read and hard to maintain, so they called it callback hell or the pyramid of Doom
JavaScript
result
let x = 5; console.log(`x = ${x}`); // step 1 console.warn('Give me two seconds, first empty my coffee and then I will multiply x by 2'); setTimeout(() => { x *= 2; console.log(`x * 2 = ${x}`); // step 2 console.warn("Give me a sec, I'm coming down and add 6 to x"); setTimeout(() => { x += 6; console.log(`x + 6 = ${x}`); // step 3 x -= 1; console.log(`x - 1 = ${x}`); // step 4 (output x to DOM) document.querySelector('pre').innerText = `x = ${x}`; }, 1000); }, 2000);
Copied!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Convert nested callbacks to promises
- ES6 introduced a nicer, and a more elegant way to do async operations without the need of nested callbacks, called promises
- Some features in JavaScript are already build upon promises (e.g. the fetch API) but sometimes you have to create your own promise like in previous example
- A promise is just an object that can created with
new Promise()
and has three states:- pending: response is not ready yet, please wait....
- fulfilled: response is ready, and the data can be used now (data is
resolved
) - rejected: an error occurred, and there is no data (data is
rejected
)
- As we already know from
fetch()
chapter, the promise can be chained with one or morethen()
handlers to do something with the resolved data and onecatch()
handler that catches the (reject
) error - This is a basic skeleton for a promise that don't except any parameters:
const promise = new Promise((resolve, reject) => { // do something that gives some data back after a certain time if (/* everything turned out fine -> resolve the data */) { resolve(data); } else { reject(new Error('Oops, something went wrong')); } }); promise // without parentheses! .then(data => console.log(data)) // the data from the 'resolved' promise is available and can be used now .catch(error => console.error(error));
Copied!
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
- This is a basic skeleton for a promise that except one or more parameters:
const promise = (param1, param2) => { return new Promise((resolve, reject) => { // do something that gives some data back after a certain time if (/* everything turned out fine -> resolve the data */) { resolve(data); } else { reject(new Error('Oops, something went wrong')); } }); } promise(param1, param2) // with parentheses of course .then(() => console.log('resolved')) // the data from the 'resolved' promise is available .catch(error => console.error(error));
Copied!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
REMARK
If you only have to resolve a promise without sending data back, you can use resolve()
instead of resolve(data)
- There are two posible solutions to transform our calculation example from nested callbacks to promises:
- Create two promises (one for every
setTimeout()
function) and return the result of the operation viaresolve(...)
inside the promise - Create only one promise (just a
delay
promise) and do the operations that are now inside thesetTimeout()
function in athen()
handler after the timer is resolved
- Create two promises (one for every
- Let's refactor the calculator with one
delay
promise that we can use multiple times
JavaScript
result
- The function
delay(ms)
expects one parameter (ms
) with a default value of1000ms
- The function returns a promise:
- the promise is rejected if the parameter
ms
in not a number (isNaN
) - the promise is fulfilled after the given timeout and resolve a string that can be used in the next
then(data)
handler
- the promise is rejected if the parameter
let x = 5; const delay = (ms = 1000) => { return new Promise((resolve, reject) => { if (isNaN(ms)) { reject(new Error('Delay only except milliseconds')); } else { setTimeout(() => resolve(`waited for ${ms} ms`), ms); } }); }; console.log(`x = ${x}`); // step 1 console.warn('Give me two seconds, first empty my coffee and then I will multiply x by 2'); delay(2000) .then((data) => { console.log(`%c ${data}`, 'color: blue'); x *= 2; console.log(`x * 2 = ${x}`); }) // step 2 .then(() => console.warn("Give me a sec, I'm coming down and add 6 to x")) .then(() => delay()) // 1000 is the default value .then((data) => { console.log(`%c ${data}`, 'color: blue'); x += 6; console.log(`x + 6 = ${x}`); }) .then(() => { // step 3 x -= 1; console.log(`x - 1 = ${x}`); // step 4 (output x to DOM) document.querySelector('pre').innerText = `x = ${x}`; }) .catch((error) => console.error(error));
Copied!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# Refactor the delay promise
delay() with error checking
delay() without error checking
shortest version
- If error checking is important, this is the function to use
const delay = (ms = 1000) => { return new Promise((resolve, reject) => { if (isNaN(ms)) { reject(new Error('Delay only except milliseconds')); } else { setTimeout(() => resolve(`waited for ${ms} ms`), ms); } }); };
Copied!
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# Run promises in parallel
- Open es6/promises/parallel.html and es6/promises/parallel.js
- Use the
all()
handler to run multiple promises at the same time (in parallel) and process the data after all promises are resolved- the parameter of the
all()
handler contains an array of promises - the resolved data of all promises (also an array) is available in the
then()
handler after the slowest promise is resolved - the order the resolved data-array is the same order as the array in the
all()
handler
- the parameter of the
JavaScript
result
const delay = (ms = 1000) => new Promise((resolve) => setTimeout(() => resolve(`waited for ${ms} ms`), ms)); // delay in parallel const runParallel = [delay(2000), delay(3000), delay()]; Promise.all(runParallel) .then((values) => { console.log(values); values.forEach((value) => console.log(value)); }) .catch((error) => console.error(error)); // delay in series (one after another) /* delay(2000) .then((data) => console.log(data)) .then(() => delay(3000)) .then((data) => console.log(data)) .catch((error) => console.error(error)); */
Copied!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Examples
# Karaoke
- Open es6/promises/karaoke.html and es6/promises/karaoke.js
HTML
JavaScript
result
<html lang="en"> <head> ... <style> #lyrics span { display: inline-block; width: 2rem; text-align: center; background-color: gray; color: white; } </style> </head> <body> .. <div class="border-green" id="lyrics"></div> ... </body> </html>
Copied!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Random words (advanced)
- Open es6/promises/random_words.html and es6/promises/random_words.js
- Suppose we want to fetch x random words from this API (opens new window)
- The result is an array of one item (opens new window) whose
word
anddescription
we place in a definition list - There is no need to make three API calls one after the other (in series), but we will make the three API calls simultaneously (in parallel)
HTML
JavaScript
result
<!DOCTYPE html> <html lang="en"> <head> .. <style> [type='range'] { width: calc(100% - 150px); min-width: 180px;} dl { margin: 0 1rem; } dt { font-weight: bold; } dd { margin: 0 0 1rem 0; } dl *:last-child { margin-bottom: 0; } </style> </head> <body> ... <div class="border-green"> <p> <label for="number">Nr of words = <output id="output">3</output></label> <input type="range" min="1" max="10" value="3" id="number" oninput="output.value = number.value" /> </p> <hr /> <dl></dl> </div> ... </body> </html>
Copied!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
READING TIPS