ES6 Promise 65 lines of code tear Promise by hand

Hand tear Promise

When it comes to looking for an internship, I don't think it's very good to look at the experience. I can remember the best thing by writing code

Hand tear target

  • Including then and catch methods
  • Support chain call
  • Asynchronous solution

Start hand tearing

First of all, if you don't know Promise, you may need to take a detour to learn it first, because you don't want to be wordy here, you have to write it directly

I think it's different from many online tutorials, because I didn't think of your ideas for the moment

Basic framework

const PENDING = "pending";
const FINISHED = "fulfilled";
const FAILED = "rejected";
class XPromise {
  constructor(handleFinish) {
    let __PromiseState__ = PENDING;
    this.__PromiseState__=__PromiseState__
    this.onResolvedCallbacks = [];
    this.onRejectCallback = undefined;
    this.__PromiseResult__ = undefined;
    const resolve = (val) => {
      ...
    };
    const reject = (val) => {
      ...
    };
    try {
      handleFinish?.(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }
  then(fun,handleError) {
   ...
  }
  catch(fun) {
    ...
  }
}

Explain:

  • __ PromiseState__ We use this variable to store the state of the Promise object, and the four states of "pending", "fully" and "rejected" are declared in advance
  • onResolvedCallBacks this is a function array containing functions from then() that have not yet been executed. The array is used to ensure chain calls
  • onRejectCallback stores a handleError function, which is executed when Promise throws an exception
  • __ PromiseResult__ The result of resolve or reject is stored

Improve resolve, reject, then and catch

  • resolve and reject
    const resolve = (val) => {
      this.__PromiseResult__ = val;
      this.__PromiseState__ = FINISHED;
    };
    this.reject = (val) => {
      this.__PromiseResult__ = val;
      this.__PromiseState__ = FAILED;
      delete this.reject
    };

Here, reject is deleted from the object after a reject run, in order to comply with the reason why rejection cannot be modified after reject in Promise specification

  • then and catch
  then(fun,handleError) {
    try {
      if (this.__PromiseState__ === PENDING){
        this.onResolvedCallbacks.push(fun);
        this.onRejectCallback = handleError||this.onRejectCallback;
      }
      else if (this.__PromiseState__ === FINISHED) {
        let res = fun?.(this.__PromiseResult__);
        if (res) this.__PromiseResult__ = res;
      }else if (this.__PromiseState__ === FAILED) handleError?.(this.__PromiseResult__)
    } catch (error) {
      this.reject?.(error);
    }
    return this;
  }
  catch(fun) {
    return this.then(undefined,fun)
  }

The first parameter of then is the function fun that should be executed by the state fully, and the latter is the second parameter of Promise then

At runtime, if the current state is pending, then fun will be stuffed into onResolvedCallbacks. If the current state is fully, fun will be executed directly. If fun has a return value, the return value will be used as a new value__ PromiseResult__

Using setter to implement our asynchronous

Object.defineProperties(this, {
      __PromiseState__: {
        set(e) {
          __PromiseState__ = e;
          if (e === FINISHED) {
            try {
              this.onResolvedCallbacks.forEach(item =>item?.(this.__PromiseResult__))
            } catch (error) {
              this.reject(error);
            }
          } else if (e === FAILED) {
            if(this.onRejectCallback instanceof Function) this.onRejectCallback?.(this.__PromiseResult__)
            else throw new Error(this.__PromiseResult__)
          }
        },
        get:()=>__PromiseState__
      },
    });

Here's an explanation. The reason why we defined an additional__ PromiseState__ Instead of using this__ PromiseState__ This is because calling this attribute inside getter also triggers getter

When we resolve or reject, the setter will be triggered, that is, the set here

At this point, we can execute our function according to the state

65 lines of complete code

const PENDING = "pending";
const FINISHED = "fulfilled";
const FAILED = "rejected";
class XPromise {
  constructor(handleFinish) {
    let __PromiseState__ = PENDING;
    this.__PromiseState__ = __PromiseState__;
    this.onResolvedCallbacks = [];
    this.__PromiseResult__ = undefined;
    const resolve = (val) => {
      this.__PromiseResult__ = val;
      this.__PromiseState__ = FINISHED;
    };
    this.reject = (val) => {
      this.__PromiseResult__ = val;
      this.__PromiseState__ = FAILED;
      delete this.reject;
    };
    Object.defineProperties(this, {
      __PromiseState__: {
        set(e) {
          __PromiseState__ = e;
          if (e === FINISHED) {
            try {
              this.onResolvedCallbacks.forEach((item) => {
                let res = item?.(this.__PromiseResult__);
                if (res) this.__PromiseResult__ = res;
              });
            } catch (error) {
              this.reject(error);
            }
          } else if (e === FAILED) {
            if (this.onRejectCallback instanceof Function)
              this.onRejectCallback?.(this.__PromiseResult__);
            else throw new Error(this.__PromiseResult__);
          }
        },
        get: () => __PromiseState__,
      },
    });
    try {
      handleFinish?.(resolve, this.reject);
    } catch (error) {
      this.reject?.(error);
    }
  }
  then(fun, handleError) {
    try {
      if (this.__PromiseState__ === PENDING) {
        this.onResolvedCallbacks.push(fun);
        this.onRejectCallback = handleError || this.onRejectCallback;
      } else if (this.__PromiseState__ === FINISHED) {
        let res = fun?.(this.__PromiseResult__);
        if (res) this.__PromiseResult__ = res;
      } else if (this.__PromiseState__ === FAILED)
        handleError?.(this.__PromiseResult__);
    } catch (error) {
      this.reject?.(error);
    }
    return this;
  }
  catch(fun) {
    return this.then(undefined, fun);
  }
}

test

So let's run and see if it works

Test 1: asynchronous and chained calls

function test() {
  let p = new XPromise((rs, rj) => {
    setTimeout(() => {
      rs(2021);
    }, 1000);
  })
    .then((res) => {
      console.log(res);
      return 2022;
    })
    .then((res) => {
      console.log(res);
    });
}
test();

Estimated output 2021, 2022

Test 2: exception thrown

function test() {
  let p = new XPromise((rs, rj) => {
    setTimeout(() => {
      rs(2021);
    }, 1000);
  }).then(res=>{
      console.log(res)
      const i=1
      i=2
  })
}
test();


As expected, 2021 will be output after 1s; Then, because we modify the const value in then, an exception will be thrown

Test 3: catch exception

function test() {
  let p = new XPromise((rs, rj) => {
    setTimeout(() => {
      rs(2021);
    }, 1000);
  })
    .then((res) => {
      const i=1
      i=0
      return 2022;
    })
    .catch(e=>{
      console.log(e)
    })
    
}
test();

Now that you've caught it, you won't be wrong in red

So now, the hand tear Promise is completed

more

Only the most basic functions of Promise are implemented here. As for methods like all and race, I won't write more

Tags: Javascript Front-end

Posted on Fri, 05 Nov 2021 16:26:12 -0400 by Pellefant