No One Kept My Promise, So I Built My Own

No One Kept My Promise, So I Built My Own

Promises are the bread and butter of modern JavaScript. They help us manage asynchronous code in a clean, structured way. But what if you feel let down by the Promises everyone else keeps? Well, you build your own! In this blog, I’ll walk you through my custom implementation of a Promise class, complete with a sprinkle of fun and plenty of code.


Why Build Your Own Promise?

Before you ask, no, I’m not trying to replace native JavaScript Promises. This was an exercise in learning. By implementing my own Promise, I got to understand the inner workings of JavaScript’s asynchronous capabilities better. Plus, it’s always fun to feel like a JavaScript wizard.

So, let’s dive into how I built my own MyPromise class from scratch.


The Anatomy of MyPromise

Here’s the full implementation of my custom MyPromise class:

class MyPromise {
  constructor(executor) {
    this.state = 'pending';
    this.handlers = [];
    try {
      executor(this._resolve.bind(this), this._reject.bind(this));
    } catch (err) {
      this._reject(err);
    }
  }

  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      this.handlers.push({
        fulfilled: (value) => {
          if (typeof onFulfilled !== 'function') {
            resolve(value);
            return;
          }
          try {
            resolve(onFulfilled(value));
          } catch (err) {
            reject(err);
          }
        },
        rejected: (error) => {
          if (typeof onRejected !== 'function') {
            reject(error);
            return;
          }
          try {
            resolve(onRejected(error));
          } catch (err) {
            reject(err);
          }
        },
      });
      this._executeHandlers();
    });
  }

  _executeHandlers() {
    if (this.state === 'pending') return;
    for (const handler of this.handlers) {
      queueMicrotask(() => {
        handler[this.state](this.result);
      });
    }

    this.handlers = [];
  }

  _resolve(value) {
    if (this.state !== 'pending') return;
    if (value instanceof MyPromise) {
      value.then(this._resolve.bind(this), this._reject.bind(this));
      return;
    }
    this.state = 'fulfilled';
    this.result = value;
    this._executeHandlers();
  }

  _reject(error) {
    if (this.state !== 'pending') return;
    this.state = 'rejected';
    this.result = error;
    this._executeHandlers();
  }

  catch(onRejected) {
    return this.then(undefined, onRejected);
  }

  static resolve(value) {
    return new MyPromise((resolve) => {
      resolve(value);
    });
  }

  static reject(value) {
    return new MyPromise((resolve, reject) => {
      reject(value);
    });
  }
}        

Now let’s break it down piece by piece.


Building Blocks of MyPromise

1. The Constructor

The constructor initializes the promise with a state of pending and an empty list of handlers.

constructor(executor) {
  this.state = 'pending';
  this.handlers = [];
  try {
    executor(this._resolve.bind(this), this._reject.bind(this));
  } catch (err) {
    this._reject(err);
  }
}        

Here’s what happens:

  • State: A promise starts in the pending state.
  • Handlers: A queue of functions to execute once the state changes.
  • Executor: The provided function, called with _resolve and _reject.
  • Error Handling: If the executor throws an error, it’s caught and rejected.


2. The then Method

The then method registers callbacks for when the promise resolves or rejects.

then(onFulfilled, onRejected) {
  return new MyPromise((resolve, reject) => {
    this.handlers.push({
      fulfilled: (value) => {
        if (typeof onFulfilled !== 'function') {
          resolve(value);
          return;
        }
        try {
          resolve(onFulfilled(value));
        } catch (err) {
          reject(err);
        }
      },
      rejected: (error) => {
        if (typeof onRejected !== 'function') {
          reject(error);
          return;
        }
        try {
          resolve(onRejected(error));
        } catch (err) {
          reject(err);
        }
      },
    });
    this._executeHandlers();
  });
}        

Key points:

  • Each then returns a new promise for chaining.
  • If the state is already settled, handlers are executed immediately.
  • Errors in handlers are caught and rejected.


3. The _resolve and _reject Methods

These methods settle the promise by updating the state and value, then executing handlers.

_resolve(value) {
  if (this.state !== 'pending') return;
  if (value instanceof MyPromise) {
    value.then(this._resolve.bind(this), this._reject.bind(this));
    return;
  }
  this.state = 'fulfilled';
  this.result = value;
  this._executeHandlers();
}

_reject(error) {
  if (this.state !== 'pending') return;
  this.state = 'rejected';
  this.result = error;
  this._executeHandlers();
}        

4. Static Methods: resolve and reject

These factory methods create settled promises.

static resolve(value) {
  return new MyPromise((resolve) => {
    resolve(value);
  });
}

static reject(value) {
  return new MyPromise((resolve, reject) => {
    reject(value);
  });
}        

5. The _executeHandlers Method

This method ensures all registered handlers are executed asynchronously.

_executeHandlers() {
  if (this.state === 'pending') return;
  for (const handler of this.handlers) {
    queueMicrotask(() => {
      handler[this.state](this.result);
    });
  }

  this.handlers = [];
}        

Testing MyPromise

Let’s see it in action:

const promise = new MyPromise((resolve, reject) => {
  setTimeout(() => resolve('Hello, MyPromise!'), 1000);
});

promise
  .then((value) => {
    console.log(value);
    return 'Chained value';
  })
  .then((value) => {
    console.log(value);
  })
  .catch((err) => {
    console.error(err);
  });        

Output:

Hello, MyPromise!
Chained value        

Conclusion

Implementing MyPromise was a fantastic way to dive deep into how Promises work. While it’s unlikely you’ll need to replace JavaScript’s native Promises, understanding them at this level gives you a huge advantage when working with asynchronous code.

And hey, now no one can say I didn’t keep my promise!

要查看或添加评论,请登录

Rohit Nandi的更多文章

社区洞察

其他会员也浏览了