0

I have a custom Promise2 class that extends Promise class to allow early settling. It uses my custom Timer class to check the progress of a simulated activity through another timer t1. In my example, p1 does an early settling but the problem is with the p1.then( doesn't recognize the onfulfilled callback as a function.

I suspected I have to override then() and call the super.then() but it didn't work. By the way, the timed executor callback inside super() is just a workaround to make this accessible. Any ideas on what's lacking in my Promise2 class?

JavaScript Code

'use strict';

const p1 = new Promise2(
  (resolve, reject) => {
    const t1 = Timer.create(
      () => {
        resolve('Promise resolved.');
        // reject(new Error('Promise rejected.'));
      },
      3000,
    );

    return { timer: t1 };
  },
);

Timer.create(
  () => {
    const { isCompleted } = p1.return.timer;
    const { progress } = p1.return.timer;

    if (isCompleted === false) {
      console.log(`Promise: ${progress} %`);

      if (progress > 50) {
        p1.resolve('Early resolve.');
        // p1.reject(new Error('Early reject.'));
        p1.return.timer.stop();
      }
    }
  },
  250,
  true,
  16,
);

// p1.promise.then(
p1.then(
  (value) => {
    console.log('__resolve__');
    console.log(value);
  },
)
.catch(
  (reason) => {
    console.log('__catch__');
    console.log(reason);
  },
);

Promise2 Class

class Promise2 extends Promise {
  constructor(executor = null) {
    super(
      (resolve, reject) => {
        Timer.create(
          () => {
            this.resolve = resolve;
            this.reject = reject;
            this.return = executor(resolve, reject);
          },
          1);
      },
    );

    // this.promise = new Promise(
    //   (resolve, reject) => {
    //     this.resolve = resolve;
    //     this.reject = reject;
    //     this.return = executor(resolve, reject);
    //   },
    // );
  }

  static create(executor = null) {
    return new Promise2(...arguments);
  }
}

Timer Class

class Timer {
  constructor(workload = null, milliseconds = 1000, isAutostart = true, repeat = 1, isInterval = false) {
    this.workload = workload;
    this.milliseconds = milliseconds;
    this.isAutostart = isAutostart;
    this.repeat = repeat;
    this.isInterval = isInterval;

    this.startTime = 0;
    this.endTime = 0;
    this.timeLeft = milliseconds;
    this.timeoutId = 0;
    this.progress = 0;
    this.isCompleted = false;
    this.endTimeActual = 0;
    this.repeatLeft = repeat;
    this.isPaused = false;
    this.subTimers = [];

    if (isAutostart === true) {
      this.start();
    }
  }

  start(thisArg = this) {
    thisArg.startTime = Date.now();
    thisArg.endTime = thisArg.startTime + thisArg.milliseconds;

    const timeoutEndTime = Date.now();

    thisArg.watch(thisArg.workload, timeoutEndTime, thisArg);
  }

  watch(workload = null, timeoutEndTime = 0, thisArg = this) {
    if (thisArg.isPaused === true) {
      return;
    }

    const timeoutLag = Date.now() - timeoutEndTime;

    thisArg.timeLeft = thisArg.endTime - Date.now() - timeoutLag;

    if (thisArg.timeLeft > 0) {
      thisArg.progress = 100 - ((thisArg.timeLeft / thisArg.milliseconds) * 100);

      const inProgress = 100 - thisArg.progress;
      const tick = thisArg.timeLeft / inProgress;

      timeoutEndTime = Date.now() + tick;
      thisArg.timeoutId = setTimeout(thisArg.watch, tick, thisArg.workload, timeoutEndTime, thisArg);

      return;
    }

    thisArg.stop(thisArg);
    workload();

    if (thisArg.repeatLeft > 0) {
      thisArg.isCompleted = false;
      thisArg.start();
    }

    if (thisArg.isInterval === true) {
      thisArg.repeatLeft = 1;
    }

    if (thisArg.subTimers.length > 0) {
      thisArg.subTimers.forEach(
        (timer) => {
          if (timer.isAutostart === true) {
            timer.start();
          }
        },
      );
    }
  }

  stop(thisArg = this) {
    clearTimeout(thisArg.timeoutId);
    thisArg.isCompleted = true;
    thisArg.endTimeActual = Date.now();
    thisArg.repeatLeft -= 1;

    if (thisArg.isInterval === true) {
      thisArg.repeatLeft = 0;
    }
  }

  restart(thisArg = this) {
    thisArg.stop();

    thisArg.startTime = 0;
    thisArg.endTime = 0;
    thisArg.timeLeft = thisArg.milliseconds;
    thisArg.timeoutId = 0;
    thisArg.progress = 0;
    thisArg.isCompleted = false;
    thisArg.endTimeActual = 0;
    thisArg.repeatLeft = thisArg.repeat;

    thisArg.start();
  }

  pause(thisArg = this) {
    thisArg.isPaused = true;
  }

  resume(thisArg = this) {
    thisArg.isPaused = false;

    const timeoutEndTime = Date.now();

    thisArg.watch(thisArg.workload, timeoutEndTime, thisArg);
  }

  static create(workload = null, milliseconds = 1000, isAutostart = true, repeat = 1, isInterval = false) {
    return new Timer(...arguments);
  }

  static chain(timers = []) {
    const timerReferences = Timer.chainWalk(timers);

    if (timerReferences[0].isAutostart === true) {
      timerReferences[0].start();
    }

    return timerReferences;
  }

  static chainWalk(timers = [], timerReferences = [], nextTimer = null) {
    if (timers.length === 0) {
      return timerReferences;
    }

    if (timerReferences.length === 0) {
      timers = [...timers];
    }

    const timer = timers.shift();

    const {
      workload = null,
      milliseconds = 1000,
      isAutostart = true,
      repeat = 1,
      isInterval = false,
    } = timer;

    const newTimer = new Timer(workload, milliseconds, false, repeat, isInterval);

    newTimer.isAutostart = isAutostart;

    if (timerReferences.length === 0) {
      timerReferences.push(newTimer);
      [nextTimer] = timerReferences;
    } else {
      nextTimer.subTimers.push(newTimer);
      [nextTimer] = nextTimer.subTimers;
    }

    timerReferences = Timer.chainWalk(timers, timerReferences, nextTimer);

    return timerReferences;
  }

  static tree(timers = []) {
    const timerReferences = Timer.treeWalk(timers);

    timerReferences.forEach(
      (reference) => {
        if (reference.isAutostart === true) {
          reference.start();
        }
      },
    );

    return timerReferences;
  }

  static treeWalk(timers = []) {
    const timerReferences = [];

    timers.forEach(
      (timer) => {
        const {
          workload = null,
          milliseconds = 1000,
          isAutostart = true,
          repeat = 1,
          isInterval = false,
          subTimers = [],
        } = timer;

        const newTimer = new Timer(workload, milliseconds, false, repeat, isInterval);

        newTimer.isAutostart = isAutostart;

        if (Array.isArray(subTimers) === true) {
          newTimer.subTimers = Timer.treeWalk(subTimers);
        }

        timerReferences.push(newTimer);
      },
    );

    return timerReferences;
  }
}

Console Output Console output of the JavaScript code


Promise2 Class (Working Alternative)

class Promise2 {
  constructor(executor = null) {
    this.promise = new Promise(
      (resolve, reject) => {
        this.resolve = resolve;
        this.reject = reject;
        this.return = executor(resolve, reject);
      },
    );

    this.then = function (onfulfilled = null, onrejected = null) {
      return this.promise.then(...arguments);
    };

    this.catch = function (onrejected = null) {
      return this.promise.catch(...arguments);
    };

    this.finally = function (onfinally = null) {
      return this.promise.finally(...arguments);
    };
  }

  static create(executor = null) {
    return new Promise2(...arguments);
  }
}
  • 1
    Just *don't* `extend Promise`. I don't see a good reason to do it here. – Bergi May 07 '22 at 20:33
  • 1
    You are right, I could just wrap a promise object to avoid dirty tricks in the `super()` call. But, I am curious about why `p1.then()`, `p1.catch()`, and `p1.finally()` suddenly became unusable. – TheGoodGameLife May 08 '22 at 05:21
  • 1
    Because they return a `new Promise2`, internally calling your constructor again. I've not yet figured out completely what your `Timer.create` does, but it might be the reason for them behaving weird. – Bergi May 08 '22 at 14:15
  • The `Timer.create()` is basically a `setTimeout()` that returns a `Timer` object that can start, stop, restart, pause, and resume the timer. But even if I changed `Timer.create()` to `setTimeout()`, it still throwing the same error. I'll just post my alternative `Promise2` class. – TheGoodGameLife May 08 '22 at 15:26
  • Extending Promise can be done as discussed [here](/q/41792036) and [here](/q/48158730). But it's not trivial and mostly not needed. Instead define a Promise-like class via [Duck typing](https://en.wikipedia.org/wiki/Duck_typing). – cachius May 08 '22 at 18:11
  • @cachius Yeah, I tried them but it didn't work to expose the `resolve` and `reject` callbacks then adding them to the resulting `Promise` object from the executor, which is only accessible during the construction phase of the super class `Promise`. – TheGoodGameLife May 09 '22 at 06:00

0 Answers0