Why Promise ?#
To begin with, let’s understand what is ?
A Promise is an object representing the eventual completion or failure of an asynchronous operation. Since most people are consumers of already-created promises, this guide will explain consumption of returned promises before explaining how to create them.
– MDN Web Docs
and why we will need the concept of Promise
?
- Promise provide you a way to reducing call-back hell
- Promises give us the ability to write cleaner code
- Promise allow you to handle error together (or optionally by each promise)
– ColorCode - JavaScript Promises
World without Promise ?#
Broken Async#
First, we begin with a simple asynchronous function getWeather_1
using setTimeout
that is broken (and we will be using this method to simulate the asynchrnouse function a lot in the upcoming examples).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // [ASYNC code (primitive with bug)]
function getWeather_1() {
setTimeout(() => {
return "Sunny";
}, 1000);
}
let weather_1 = getWeather_1();
document.body.innerText(weather_1);
// What is wrong ?
// - this function will always set weather to "undefined"
// - this ie because function "getWeather"
// will only return after the interval (delay=100) is met
|
Primitive Async#
Second, let’s try fixing bug in the previous asynchronouse code example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| // [ASYNC code (primitive)]
function getWeather_2() {
setTimeout(() => {
weather_2 = "Sunny";
document.body.innerText(weather_2);
}, 1000);
}
let weather_2;
getWeather_2();
// How this is fixed ?
// - now the set text "innerText" command will only execute
// after the weather_2 varaible has been assigned with "Sunny"
// What is the wrong ?
// - now the function is a bit cluttered in a way it is both:
// 1. non-modular (both setText and getData are in the same place)
// 2. have side-effect (it will change the global variable weather_2)
|
Async Enhanced#
Now we start to resolve the persisting issue with the code, first let’s try tackle the problem of side-effect with another function:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| // [ASYNC code (with callback)]
function weatherReceived_nowChangeText(data){
let weather = data;
document.body.innerText(weather);
}
function getWeather_3(){
setTimeout(()=>{
let weather_3 = "Sunny";
weatherReceived_nowChangeText(weather_3)
}, 1000)
}
getWeather_3();
// What is solved ?
// - now the function has no side-effect,
// - and "seems" a bit modular now
// What's still wrong ?
// - the function only "seems" modular, but they are not !!!
// because executing function "getWeather_3" will necessary update text via "weatherReceived_nowChangeText"
// (i.e. you cannot use getWeather_3 to do anything else, say for intance to "console.log" or "alert")
// - the two function looks modular, but are in nature still tightly coupled
|
Async with callback#
Despite the side-effect, we can also resolve the modularity (strong couple/dependency) via callback function
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| // [Async with callback]
function weatherReceived(data) {
let weather = data;
document.body.innerText(weather);
}
function getWeather(callback) {
setTimeout(() => {
callback('Sunny');
}, 1000);
}
getWeather (weatherReceived)
getWeather ((data)=>{console.log(data)}) // [Example Alternative Callback - 1]
getWeather ((data)=>{alert(data)}) // [Example Alternative Callback - 2]
// What is solved ?
// - now the function is side-effect free
// - and in the meanwhile modular !
// (you can pass in something differet like the two alternative example shown)
// What is still wrong ?
// - despite it have solved the previous issue about "side-effect" and "modularity/dependency"
// you could likely face the "callback hell" showcased next ....
|
Async with callback hell#
using callback is benificial, but can result in **callback hell **(or Pyramid of doom, Heavy nesting, Deeply nested callbacks):
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
39
40
| // [Async with callback hell]
function requestDataA(callback) {
// process
getDataFromServer('urlA', function (error, responseA) {
if (error) {
callback(error);
} else {
// process responseA
requestDataB(responseA, function (error, responseB) {
if (error) {
callback(error);
} else {
// process responseB
requestDataC(responseB, function (error, responseC) {
if (error) {
callback(error);
} else {
// process responseC
requestDataC(responseC, function (error, responseD) {
if (error) {
callback(error);
} else {
// Final processing with responseD
callback(null, responseD);
}
});
}
});
}
});
}
});
}
requestDataA();
// What is the issue ?
// - in order to make sure every earlier getData "success/resolve", before getting the subsequent getData
// - the code have to be put in a fasion that will eventually become "infinite nesting" (aka "callback hell")
// - moreover, each time there's an error you will have to write a handle statement
|
Async with Promise#
From the following example you can see all the issue mentioned aboved getting resolved by promise:
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
| // [ASYNC with Promise]
function requestDataA() {
return getDataFromServerPromise('urlA');
}
function requestDataB(data) {
return getDataFromServerPromise('urlB?data=' + data);
}
function requestDataC(data) {
return getDataFromServerPromise('urlC?data=' + data);
}
requestDataA()
.then(responseA => {
return requestDataB(responseA);
})
.then(responseB => {
return requestDataC(responseB);
})
.then(finalResponse => {
console.log('Final data processed:', finalResponse);
})
.catch(error => {
console.error('Error during requests:', error);
});
// What problem does this resolve ?
// 1. [avoid nesting] : promise allow you to chain the "then" method,
// so each subseqeunt operation can be handled at a "higher-level" ("same-level" rather than "nesting-level")
// 2. [unified error handling] : annd error is handled at one place in the "catch" method
// 3. [redability] : the code is much more readable and understand, making modification/maintennance of the code much more friendly
//
// Metaphoric notation for promise ?
// - [promise maker ] : promise eventual resolve/reject
// - [promise receiver] : receive immediate promise object from the "promise maker"
//
|
Benifit of promise (by ColorCode)
- avoid the callback hell (or pyramid of doom)
- readability of code (modifying is easier)
- no callback passed into the function (the only thing passing in is the relevant argument that need to be used for the promise, say URL), hence making our function signiture a lot simplier
- all error handled together (or optionally seprately see, see the ↓↓ example below ↓↓)
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
| // ======================
// [Promise error handling]
//
// [Error Handling in Promise]:
// You can either:
// - give specific error-handle function for each promise in the chain
// - use the final "catch" block as the place where errors are resolved
let getDate;
let getCountry;
let getLocation;
let getHistoricalWeather;
let getHistoricalRaindrop;
// Error handling in one place [most recommended !]
getDate.then(
(data) =>{getCountry(data)},
).then(
(data) =>{getLocation(data)},
)
.then(
(data) =>{getHistoricalWeather(data)},
).then(
(data) =>{getHistoricalRaindrop(data)},
).catch(
(error)=>{
if( error == "no [date] data found"){
console.log("...");
} else if (error == "no [country] data found"){
console.log("...");
} else if (error == "no [location] data found"){
console.log("...");
} else if (error == "no [weather] data found"){
console.log("...");
} else if (error == "no [raindrop] data found"){
console.log("...");
}
}
);
// Specific error handling...
getDate.then(
(data) =>{getCountry(data)},
(error) =>{console.log(error);}
).then(
(data) =>{getLocation(data)},
(error) =>{console.log(error);}
)
.then(
(data) =>{getHistoricalWeather(data)},
(error) =>{console.log(error);}
).then(
(data) =>{getHistoricalRaindrop(data)},
(error) =>{console.log(error);}
);
// Error handling in a mixed manner
getDate.then(
(data) =>{getCountry(data)},
).then(
(data) =>{getLocation(data)},
)
.then(
(data) =>{getHistoricalWeather(data)},
(error) =>{console.log(error);} // ← handle error here ....
).then(
(data) =>{getHistoricalRaindrop(data)},
).catch(
(error)=>{
if( error == "no [date] data found"){
console.log("...");
} else if (error == "no [country] data found"){
console.log("...");
} else if (error == "no [location] data found"){
console.log("...");
// } else if (error == "no [weather] data found"){ ← Don't need anymore ...
// console.log("..."); ← Don't need anymore ...
} else if (error == "no [raindrop] data found"){
console.log("...");
}
}
);
// ======================
|
Reference#