Search code examples
javascriptsettimeoutdelaychaining

Why all methods in chain executed at the same time?


Why all methods in chain executed at the same time? I wrap method in setTimeout. What I did wrong?

I know how to do this with promises but I'm learning chaining technique.

const delay = function delay(func) {
  return function(...args) {
    setTimeout(() => {
      return func.apply(this, [...args]);
    }, 2000);
    return this;
  };
}

class TimePopup {

  constructor() {
    this.firstMethod = delay(this.firstMethod);
    this.secondMethod = delay(this.secondMethod);
    this.thirdMethod = delay(this.thirdMethod);
    console.log('This is the constructor');
  }

  firstMethod(val) {
    console.log('This is the First Method', val);
  }

  secondMethod(val) {
    console.log('This is the Second Method', val);
  }

  thirdMethod(val) {
    console.log('This is the Third Method', val);
  }
}

new TimePopup()
  .firstMethod(1)
  .secondMethod(2)
  .thirdMethod(3);


Solution

  • The issue you have is that delay does not schedule things to run relative to each other. Each thing is delayed exactly the time it's called, so new TimePopup().firstMethod(1).secondMethod(2).thirdMethod(3); will call all three methods at the same time and each is delayed the same amount of time relative to the call time. Then all three fire after two seconds.

    Instead, you need to process them sequentially. Similar to this answer of mine, you can use create a promise queue to handle the sequence. Each method adds to the chain instead and there is a delay between before it's executed:

    const wait = ms => () =>
      new Promise(resolve => setTimeout(resolve, ms));
      
    let queue = Promise.resolve();
      
    function delay(func) {
      return function(...args) {
        queue = queue
          .then(wait(2000))
          .then(() => func.apply(this, [...args]));
        return this;
      };
    }
    
    class TimePopup {
    
      constructor() {
        this.firstMethod = delay(this.firstMethod);
        this.secondMethod = delay(this.secondMethod);
        this.thirdMethod = delay(this.thirdMethod);
        console.log('This is the constructor');
      }
    
      firstMethod(val) {
        console.log('This is the First Method', val);
      }
    
      secondMethod(val) {
        console.log('This is the Second Method', val);
      }
    
      thirdMethod(val) {
        console.log('This is the Third Method', val);
      }
    }
    
    new TimePopup()
      .firstMethod(1)
      .secondMethod(2)
      .thirdMethod(3);

    A slightly more reusable approach is to create the delay functions on demand, so you can have different queues for different tasks. This can look like this:

    const delayMaker = delayBetween => {
      let queue = Promise.resolve();
    
      return function delay(func) {
        return function(...args) {
          queue = queue
            .then(wait(delayBetween))
            .then(() => func.apply(this, [...args]));
          return this;
        };
      }
    }
    
    /* ... */
    
    const delay = delayMaker(2000);
    

    const wait = ms => () =>
      new Promise(resolve => setTimeout(resolve, ms));
    
    const delayMaker = delayBetween => {
      let queue = Promise.resolve();
    
      return function delay(func) {
        return function(...args) {
          queue = queue
            .then(wait(delayBetween))
            .then(() => func.apply(this, [...args]));
          return this;
        };
      }
    }
    
    class TimePopup {
    
      constructor() {
        const delay = delayMaker(2000);
        
        this.firstMethod = delay(this.firstMethod);
        this.secondMethod = delay(this.secondMethod);
        this.thirdMethod = delay(this.thirdMethod);
        console.log('This is the constructor');
      }
    
      firstMethod(val) {
        console.log('This is the First Method', val);
      }
    
      secondMethod(val) {
        console.log('This is the Second Method', val);
      }
    
      thirdMethod(val) {
        console.log('This is the Third Method', val);
      }
    }
    
    new TimePopup()
      .firstMethod(1)
      .secondMethod(2)
      .thirdMethod(3);