03/09/2025
Promises have been such critical part of how we orchestrate the user interaction between the client application and the many asynchronous operations, web applications without it wouldn't work. Promises enable us to bridge the waiting period when the user starts an action that takes time to complete and the next procedures. Due to JavaScript's asynchronous nature, without proper callbacks or promises, actions will either won't work or leave the user hanging.
Simple example of a promise in action:
1const promise = new Promise((res, rej) => {
2 setTimeout(() => {
3 res(true)
4 }, 2000)
5})
6
7promise.then((res) => {
8 console.log(`Done with response: ${res}`)
9})
10
11// After 2 seconds, the console will display this in the log:
12// Done with response: trueHere in this super simple example, the the res callback/function is invoked after the setTimeout is complete, which then returned to the .then function. At this point, the promises is considered to be complete or fulfilled. The completeness of promises is what allows the application to properly know the timing of which next steps the application needs to do.
To further understand Promises, we'll deep dive into a functional working example of promises built from scratch. Understanding the deep inner working of promises also allows us to revisit the importance of how JavaScript classes and callbacks work.
So let's get started
For this purpose, we'll call it Future as the class name and it should follow almost the same functionality, along with these different way Future can be consumed: .catch(), .finally() and also promise chaining.
1// Our promise from scratch
2class Future {
3 constructor() {
4 // ... init stuff here
5 }
6}
7
8const promise = new Future ((res, rej) => {
9 setTimeout(() => {
10 res(true)
11 }, 2000)
12})
13
14promise.then((res) => {
15 console.log(`Done with response: ${res}`)
16})1class Future {
2 constructor(executor) {
3 this.state = 'pending';
4 this.value = null;
5 this.handleFulfilled = () => {};
6 this.handleRejected = () => {};
7 this.alwaysHandler = () => {};
8
9 const resolve = res => {
10 this.state = 'fulfilled';
11 this.value = res;
12
13 // Call the success/fulfilled handler
14 this.handleFulfilled(res);
15 this.alwaysHandler();
16 };
17
18 const reject = res => {
19 this.state = 'rejected';
20 this.value = res;
21
22 // Call the failed/rejected handler
23 this.handleRejected(res);
24 this.alwaysHandler();
25 };
26
27 try {
28 executor(resolve, reject);
29 } catch (err) {
30 reject(err);
31 }
32 }
33
34 then(success, rejected) {
35 // if (this.state === 'pending') {
36 this.handleFulfilled = success || (() => {});
37 this.handleRejected = rejected || (() => {});
38 // }
39
40 // if (this.state === 'fulfilled') {
41 // success(this.value);
42 // } else if (this.state === 'rejected') {
43 // rejected(this.value);
44 // }
45
46 return this;
47 }
48
49 catch(rejected) {
50 // if (this.state === 'pending') {
51 // console.log(rejected);
52 this.handleRejected = rejected;
53 // }
54
55 // if (this.state === 'rejected') {
56 // rejected(this.value);
57 // }
58
59 return this;
60 }
61
62 finally(alwaysHandler) {
63 this.alwaysHandler = alwaysHandler;
64
65 return this;
66 }
67}
68
69const promise = new Future((res, rej) => {
70 setTimeout(() => rej(true), 2000);
71});
72
73// promise
74// .then(
75// res => {
76// console.log(`done with response: ${res}`);
77// },
78// res => {
79// console.log('here1!');
80// console.log(`failed with response: ${res}`);
81// }
82// )
83// .catch(res => {
84// console.log('here2!');
85// console.log(`failed with response: ${res}`);
86// });
87
88promise
89 .then(res => {
90 console.log(`done with response: ${res}`);
91 })
92 .catch(res => {
93 console.log(`done with response: ${res}`);
94 })
95 .finally(() => {
96 console.log("here in the finally")
97 })
98