3

How do you implement delay like the jQuery Library? - I know this question has been asked so many times, but haven't seen anyone implement it using async/await or ES6 stylistics. let me know if you have ideas

//create a jquery like library
class DOM {
    constructor(selector){
        this.elements = [];
        if(!selector){
            return;
        } else if(selector === 'document' || selector === 'window'){
            this.elements = [selector];
        } else {
            this.elements = Array.from(document.querySelectorAll(selector));
        }
    }

    on(){
        console.log('on');
        return this;
    }

    get(){
        console.log('get');
        return this;
    }


    delay(ms, callback){
        //how to implement this? how to chain result of callback onto next chain?
        console.log('delay');
        const promise = Promise.resolve();
        return promise.then(function(resolve) {
            setTimeout(function(){
                resolve(this);
            }, ms);
        });
    }
}

const $ = function(selector) {
    return new DOM(selector);
}       

$('document').on().delay(10000).get()
guest
  • 2,185
  • 3
  • 23
  • 46
  • What do you expect this code to do? What should `console.log` output? Or did you expect that `console.log` *itself* would be delayed?? – trincot Apr 26 '19 at 05:15
  • it should print out `on`...after 10 seconds...`get`. I deleted that text, what I meant is when used it should not be `delay(ms).then(next)` rather `delay(ms).next()` – guest Apr 26 '19 at 05:19
  • Possible duplicate of [Delay to next function in method chain](https://stackoverflow.com/questions/14365318/delay-to-next-function-in-method-chain) – Herohtar Apr 26 '19 at 05:31
  • Also see the accepted answer to [this question](https://stackoverflow.com/questions/6921275/is-it-possible-to-chain-settimeout-functions-in-javascript). – Herohtar Apr 26 '19 at 05:34

2 Answers2

3

You probably don't need promises or async/await at all, I think you can create a Proxy object that intercepts subsequent call.

The idea is that, when .delay(duration) is called, it'll return a proxy object instead of the class instance. This proxy object will intercept a method call, set time out for duration, then call the method with the original class instance.

class J {
  constructor(selector) {
    this.$element = document.querySelector(selector)
  }
  delay(duration) {
    const proxy = new Proxy(this, {
      get: (target, prop) => {
        const f = target[prop]
        return (...args) => {
          setTimeout(() => {
            return f.apply(target, [...args])
          }, duration)

          // return the class instance again, so subsequent call & delay still works
          return this
        }
      }
    })
    return proxy
  }
  text(content) {
    this.$element.textContent = content
    return this
  }
}

const $ = selector => new J(selector)

$('#test').text('hello').delay(1000).text('world')
<div id="test"></div>
Derek Nguyen
  • 11,294
  • 1
  • 40
  • 64
2

You could maintain a queue of functions still to execute on the selected element(s). That way you can allow multiple delays in the chain and also allow the client to stop the action.

A proxy can be used to "decorate" the methods which make sense to be delayed, so that they can be put in the queue instead of executed whenever a timer is still active.

Here is how that could look:

class DOM {
    constructor(selector) {
        this.elements = typeof selector === "object" ? [selector]
            : selector === 'document' || selector === 'window' ? [document] 
            : Array.from(document.querySelectorAll(selector));
        this.delayed = false;
        this.queue = [];
        const proxy = new Proxy(this, {
            get(obj, prop) {
                return !["css","show","hide","delay"].includes(prop) || !obj.delayed ? obj[prop]
                    : function (...args) {
                        obj.queue.push(() => proxy[prop](...args));
                        return this;
                    }
            }
        });
        return proxy;
    }
    each(cb) {
        this.elements.forEach(cb);
        return this;
    }
    css(name, value) {
        return this.each(elem => elem.style[name] = value);
    }
    show() {
        return this.css("display", "");
    }
    hide() {
        return this.css("display", "none");
    }
    on(eventType, cb) {
        return this.each(elem => elem.addEventListener(eventType, cb.bind(elem)));
    }
    delay(ms) {
        this.delayed = true;
        setTimeout(() => {
            this.delayed = false;
            while (this.queue.length && !this.delayed) this.queue.shift()();
        }, ms);
        return this;
    }
    stop() {
        this.queue.length = 0;
        return this;
    }
}

const $ = selector => new DOM(selector);    

const $span = $('#demo').hide();
for (let i = 0; i < 100; i++) {
    $span.delay(500).show()
         .delay(500).css("color", "red")
         .delay(500).css("color", "blue")
         .delay(500).hide();
}
$("#stop").on("click", function () {
    $span.stop();
    $(this).hide();
});
<div>This is a <span id="demo">colorful </span>demo</div>
<button id="stop">Stop</button>
trincot
  • 317,000
  • 35
  • 244
  • 286