Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build my own Promise class Part II #7

Open
wanxingliu94 opened this issue Jul 20, 2021 · 0 comments
Open

Build my own Promise class Part II #7

wanxingliu94 opened this issue Jul 20, 2021 · 0 comments

Comments

@wanxingliu94
Copy link
Owner

This is a continuation of my first post on building a custom MyPromise class which imitates the behavior of the standard Promise class that is used to handle asynchronous logic in javascript. In this post we will be implementing the all, resolve, finally, catch method. Recall that we ended with the following code last time:

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

class MyPromise {
  status = PENDING;
  value = undefined;
  reason = undefined;
  successCallback = [];
  failureCallback = [];

  constructor(executor) {
    try {
      executor(this.resolve, this.reject);
    } catch (e) {
      this.reject(e);
    }
  }

  resolve = (value) => {
    if (this.status === PENDING) {
      this.status = FULFILLED;
    } else {
      return;
    }

    this.value = value;
    while (this.successCallback.length) {
      this.successCallback.shift()();
    }
  };

  reject = (reason) => {
    if (this.status === PENDING) {
      this.status = REJECTED;
    } else {
      return;
    }
    this.reason = reason;
    while (this.failureCallback.length) {
      this.failureCallback.shift()();
    }
  };

  then = (successCallback, failureCallback) => {
    successCallback = successCallback ? successCallback : (value) => value;
    failureCallback = failureCallback
      ? failureCallback
      : (reason) => {
          throw reason;
        };
    let promise2 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        setTimeout(() => {
          try {
            let x = successCallback(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      } else if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            let x = failureCallback(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      } else {
        this.successCallback.push(() => {
          setTimeout(() => {
            try {
              let x = successCallback(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });

        this.failureCallback.push(() => {
          setTimeout(() => {
            try {
              let x = failureCallback(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
      }
    });
    return promise2;
  };
}

function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(
      new TypeError("Chaining cycle detected for promise #<MyPromise>")
    );
  }
  if (x instanceof MyPromise) {
    x.then(resolve, reject);
  } else {
    resolve(x);
  }

module.exports = MyPromise;

The All method

We want to implement the All method that comes with the standard Promise class:

function p1(){
        return new MyPromise(function(resolve, reject){
                setTimeout(function(){
                        resolve('p1');
                }, 2000);
        })
}

function p2(){
        return new MyPromise(function(resolve, reject){
                resolve('p2');
        });
};

MyPromise.all(['a','b', p1(), p2(), 'c']).then(function(result){
	console.log(result);
});

. As we can see, the all method will take an array of variables with each of them either being a MyPromise object or not. As the name suggests, if any of the MyPromise in the array rejects, it will return a REJECTED MyPromise, otherwise a FULFILLED MyPromise. If we run the above program, the expected result is: [ 'a', 'b', 'p1', 'p2', 'c' ]. Notice that we are calling all on MyPromise directly, so it is a static method.

static all(array){
    let result = [];
    function addData(key, value){
    result[key] = value;
	}
    return new MyPromise((resolve, reject)=>{
        for(let i = 0; i < array.length; i++){
            let current = array[i];
            if(current instanceof MyPromise){
                //it it is a promise object, call its then method
                current.then(value => addData(i, value), reason => reject(reason))
            }else{
                //if it is an ordinary value, store it directly
                addData(i, array[i]);
            }
        }
        resolve(result);
    })
};

The resolve method

We want to implement another static method called resolve:

function p1() {
  return new MyPromise(function (resolve, reject) {
          resolve("hello");
  });
}

MyPromise.resolve(10).then(value => console.log(value));
MyPromise.resolve(p1()).then(value => console.log(value));

, and it accepts a variable(either MyPromise or not). If it receives an ordinary value like the 10 above, it will create a MyPromise which resolves the corresponding value. If it recieves a MyPromise object, it will return that object directly.

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

The finally method

Observe the following use case for the finally method we are going to implement

function p1() {
  return new MyPromise(function (resolve, reject) {
    setTimeout(function () {
      resolve("p1");
    }, 2000);
  });
}

function p2() {
  return new MyPromise(function (resolve, reject) {
    resolve("p2 resolve");
  });
}

p2()
  .finally(() => {
    console.log("finally");
    return p1();
  })
  .then(
    (value) => {
      console.log(value);
    },
    (reason) => {
      console.log(reason);
    }
  );

, finally will be executed whatever the status of p2 and it will return a new MyPromise which carries the info from p2 to the next call to then. The expected output of the above program is

finally
p2 resolve

. Notice that if finally returns a MyPromise object with asynchronous process inside of it, the next then will wait for that process to terminate before it is executed.

finally(callback) {
    return this.then(
        (value) => {
            //using resolve here because we want to ensure that callback is executed first
            return MyPromise.resolve(callback()).then(() => value);
        },
        (reason) => {
            //using resolve here because we want to ensure that callback is executed first
            return MyPromise.resolve(callback()).then(() => {
                throw reason;
            });
        }
    );
}

The catch method

Finally, the catch method returns a MyPromise deals with rejected cases only:

function p1(){
return new MyPromise(function(resolve, reject){
	reject("failure");
    }
}
p1()
  .then((value) => console.log(value))
  .catch((reason) => console.log(reason));

and the expected output is:

failure

. The implementation is straightforward:

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

.In conclusion, we arrive at the following code for out custom MyPromise class:

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

class MyPromise {
  status = PENDING;
  value = undefined;
  reason = undefined;
  successCallback = [];
  failureCallback = [];

  constructor(executor) {
    //reject the errror if there is one
    try {
      executor(this.resolve, this.reject);
    } catch (e) {
      this.reject(e);
    }
  }

  resolve = (value) => {
    if (this.status === PENDING) {
      this.status = FULFILLED;
    } else {
      return;
    }

    this.value = value;
    while (this.successCallback.length) {
      this.successCallback.shift()();
    }
  };

  reject = (reason) => {
    if (this.status === PENDING) {
      this.status = REJECTED;
    } else {
      return;
    }
    this.reason = reason;
    while (this.failureCallback.length) {
      this.failureCallback.shift()();
    }
  };

  then = (successCallback, failureCallback) => {
    successCallback = successCallback ? successCallback : (value) => value;
    failureCallback = failureCallback
      ? failureCallback
      : (reason) => {
          throw reason;
        };
    let promise2 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        setTimeout(() => {
          try {
            let x = successCallback(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      } else if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            let x = failureCallback(this.reason);
            resolvePromise(x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      } else {
        this.successCallback.push(() => {
          setTimeout(() => {
            try {
              let x = successCallback(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });

        this.failureCallback.push(() => {
          setTimeout(() => {
            try {
              let x = failureCallback(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
      }
    });
    return promise2;
  };

  finally(callback) {
    return this.then(
      (value) => {
        return MyPromise.resolve(callback()).then(() => value);
      },
      (reason) => {
        return MyPromise.resolve(callback()).then(() => {
          throw reason;
        });
      }
    );
  }

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

  static all(array) {
    let result = [];
    let index = 0;
    return new MyPromise((resolve, reject) => {
      function addData(key, value) {
        result[key] = value;
        index++;
        if (index === array.length) {
          resolve(result);
        }
      }
      for (let i = 0; i < array.length; i++) {
        let current = array[i];
        if (current instanceof MyPromise) {
          current.then(
            (value) => addData(i, value),
            (reason) => reject(reason)
          );
        } else {
          //if it is an ordinary value, store it directly
          addData(i, array[i]);
        }
      }
    });
  }

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

function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(
      new TypeError("Chaining cycle detected for promise #<MyPromise>")
    );
  }
  if (x instanceof MyPromise) {
    x.then(resolve, reject);
  } else {
    resolve(x);
  }
}

module.exports = MyPromise;

, and this concludes the content of this post.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant