JavaScript Development Space

Building Your Own Promise: A Deep Dive into Asynchronous JavaScript

20 February 20254 min read
Mastering Asynchronous JavaScript: Handwritten Promise Implementation

In modern JavaScript development, Promises provide an elegant solution for handling asynchronous operations. This article delves into manually implementing a Promise from scratch, covering essential features like state transitions, asynchronous execution, chaining, and additional methods such as catch, resolve, all, and race.

1. Understanding the Promise Skeleton

A Promise object must have:

  • A constructor function accepting an executor callback.
  • Three states: pending, fulfilled, and rejected.
  • A .then() method to handle success and failure.

Basic Promise Structure

js
1 function MyPromise(executor) {
2 this.state = 'pending';
3 this.value = null;
4 this.reason = null;
5
6 const resolve = value => {
7 this.value = value;
8 };
9 const reject = reason => {
10 this.reason = reason;
11 };
12
13 executor(resolve, reject);
14 }
15
16 MyPromise.prototype.then = function(onFulfilled, onRejected) {
17 onFulfilled(this.value);
18 onRejected(this.reason);
19 };

This implementation lacks state transitions and asynchronous behavior, which we will address next.

2. Handling State Changes

A Promise must:

  • Transition from pending to fulfilled or rejected irreversibly.
  • Execute onFulfilled only if the state is fulfilled, and onRejected if rejected.

Improved Version

js
1 function MyPromise(executor) {
2 this.state = 'pending';
3 this.value = null;
4 this.reason = null;
5
6 const resolve = value => {
7 if (this.state === 'pending') {
8 this.value = value;
9 this.state = 'fulfilled';
10 }
11 };
12
13 const reject = reason => {
14 if (this.state === 'pending') {
15 this.reason = reason;
16 this.state = 'rejected';
17 }
18 };
19
20 executor(resolve, reject);
21 }
22
23 MyPromise.prototype.then = function(onFulfilled, onRejected) {
24 if (this.state === 'fulfilled') {
25 onFulfilled(this.value);
26 }
27 if (this.state === 'rejected') {
28 onRejected(this.reason);
29 }
30 };

3. Implementing Asynchronous Execution

To ensure .then() executes asynchronously, we use setTimeout:

js
1 function MyPromise(executor) {
2 this.state = 'pending';
3 this.value = null;
4 this.reason = null;
5 this.onFulfilledCallbacks = [];
6 this.onRejectedCallbacks = [];
7
8 const resolve = value => {
9 setTimeout(() => {
10 if (this.state === 'pending') {
11 this.value = value;
12 this.state = 'fulfilled';
13 this.onFulfilledCallbacks.forEach(cb => cb(value));
14 }
15 });
16 };
17
18 const reject = reason => {
19 setTimeout(() => {
20 if (this.state === 'pending') {
21 this.reason = reason;
22 this.state = 'rejected';
23 this.onRejectedCallbacks.forEach(cb => cb(reason));
24 }
25 });
26 };
27
28 executor(resolve, reject);
29 }
30
31 MyPromise.prototype.then = function(onFulfilled, onRejected) {
32 return new MyPromise((resolve, reject) => {
33 if (this.state === 'fulfilled') {
34 setTimeout(() => {
35 try {
36 const result = onFulfilled(this.value);
37 resolve(result);
38 } catch (error) {
39 reject(error);
40 }
41 });
42 }
43
44 if (this.state === 'rejected') {
45 setTimeout(() => {
46 try {
47 const result = onRejected(this.reason);
48 resolve(result);
49 } catch (error) {
50 reject(error);
51 }
52 });
53 }
54
55 if (this.state === 'pending') {
56 this.onFulfilledCallbacks.push(value => {
57 try {
58 const result = onFulfilled(value);
59 resolve(result);
60 } catch (error) {
61 reject(error);
62 }
63 });
64
65 this.onRejectedCallbacks.push(reason => {
66 try {
67 const result = onRejected(reason);
68 resolve(result);
69 } catch (error) {
70 reject(error);
71 }
72 });
73 }
74 });
75 };

4. Implementing Promise Chaining

To enable promise chaining, we introduce the resolvePromise function:

js
1 function resolvePromise(promise, result, resolve, reject) {
2 if (result === promise) {
3 return reject(new TypeError('Chaining cycle detected'));
4 }
5
6 if (result instanceof MyPromise) {
7 result.then(resolve, reject);
8 return;
9 }
10
11 if (typeof result === 'object' || typeof result === 'function') {
12 try {
13 let then = result.then;
14 if (typeof then === 'function') {
15 then.call(result, resolve, reject);
16 } else {
17 resolve(result);
18 }
19 } catch (error) {
20 reject(error);
21 }
22 } else {
23 resolve(result);
24 }
25 }

Now, we modify .then() to use resolvePromise:

js
1 MyPromise.prototype.then = function(onFulfilled, onRejected) {
2 return new MyPromise((resolve, reject) => {
3 if (this.state === 'fulfilled') {
4 setTimeout(() => {
5 try {
6 const result = onFulfilled(this.value);
7 resolvePromise(this, result, resolve, reject);
8 } catch (error) {
9 reject(error);
10 }
11 });
12 }
13
14 if (this.state === 'rejected') {
15 setTimeout(() => {
16 try {
17 const result = onRejected(this.reason);
18 resolvePromise(this, result, resolve, reject);
19 } catch (error) {
20 reject(error);
21 }
22 });
23 }
24
25 if (this.state === 'pending') {
26 this.onFulfilledCallbacks.push(value => {
27 try {
28 const result = onFulfilled(value);
29 resolvePromise(this, result, resolve, reject);
30 } catch (error) {
31 reject(error);
32 }
33 });
34
35 this.onRejectedCallbacks.push(reason => {
36 try {
37 const result = onRejected(reason);
38 resolvePromise(this, result, resolve, reject);
39 } catch (error) {
40 reject(error);
41 }
42 });
43 }
44 });
45 };

5. Implementing Additional Methods

.catch()

js
1 MyPromise.prototype.catch = function(onRejected) {
2 return this.then(null, onRejected);
3 };

Promise.resolve()

js
1 MyPromise.resolve = function(value) {
2 return new MyPromise(resolve => resolve(value));
3 };

Promise.reject()

js
1 MyPromise.reject = function(reason) {
2 return new MyPromise((_, reject) => reject(reason));
3 };

Promise.all()

js
1 MyPromise.all = function(promises) {
2 return new MyPromise((resolve, reject) => {
3 let results = [];
4 let completed = 0;
5
6 promises.forEach((promise, index) => {
7 promise.then(value => {
8 results[index] = value;
9 completed++;
10 if (completed === promises.length) resolve(results);
11 }, reject);
12 });
13 });
14 };

Promise.race()

js
1 MyPromise.race = function(promises) {
2 return new MyPromise((resolve, reject) => {
3 promises.forEach(promise => {
4 promise.then(resolve, reject);
5 });
6 });
7 };

Conclusion

This implementation covers the fundamental concepts of Promises, including state management, asynchronous execution, chaining, and additional methods. With this foundation, you can now understand and debug Promise-based JavaScript more effectively.

JavaScript Development Space

JSDev Space – Your go-to hub for JavaScript development. Explore expert guides, best practices, and the latest trends in web development, React, Node.js, and more. Stay ahead with cutting-edge tutorials, tools, and insights for modern JS developers. 🚀

Join our growing community of developers! Follow us on social media for updates, coding tips, and exclusive content. Stay connected and level up your JavaScript skills with us! 🔥

© 2025 JavaScript Development Space - Master JS and NodeJS. All rights reserved.