25

I was asked to create such an object called foo that can chain the function log and wait.

For example:

foo.log('breakfast').wait(3000).log('lunch').wait(3000).log('dinner');

In this scenario it prints breakfast first, waits 3 seconds, prints lunch, and then after 3 seconds it prints dinner.

I tried something like this, but it doesn't work. What did I miss?

var foo = {
  log: function(text){
    console.log(text);
    return foo;
  },

  wait: function(time) {
    setTimeout(function() {
      return foo;
    }, time);
  }
}

foo.log('breakfast').wait(3000).log('lunch').wait(3000).log('dinner');

4
  • 1
    The problem is in wait function I guess - setTimeout returns immediately and it returns nothing, so calling log on nothing gives an error. Commented Dec 31, 2021 at 10:10
  • @kiner_shah what's the correct way to achieve it? I have no idear how can I make it wait and then return the object again. Commented Dec 31, 2021 at 10:13
  • 2
    Maybe make wait a async function and then define sleep function as in this post: stackoverflow.com/a/39914235 Commented Dec 31, 2021 at 10:15
  • it looks a bit like this question: stackoverflow.com/q/32081949/1447675 Commented Jan 4, 2022 at 17:46

6 Answers 6

35

It's always better to use promises. An implementation of this functionality could be;

class Foo {
  constructor(){
    this.promise = Promise.resolve();
  }
  log(txt){
    this.promise = this.promise.then(_ => console.log(txt))
    return this;
  }
  wait(ms){
    this.promise = this.promise.then(_ => new Promise(v => setTimeout(v,ms)));
    return this;
  }
}
  
  var foo = new Foo();
  foo.log("happy").wait(1000).log("new").wait(1000).log("year");

Sign up to request clarification or add additional context in comments.

3 Comments

Woah what socery is this? Thanks so much and I have to check the Promise and take it in for a moment! Salute and happy new year to you sir!
You might want to make the object thenable, so that one can await foo. Also consider making it immutable, so that you can reuse an object without conflicts between two strands of execution.
@Bergi Thank you. Actually i tried to simplify a continuity pattern (having a resolved promise at hand in advance) within the context of the question. Perhaps it would be an overkill to present any further here. However i would still prefer to have the promise property as a private instance field like this.#promise rather freezing the object. As per thenability, the approach i have here is in fact forms the building blocks of my Asynchronous Queue (AQ) library which i believe does such things.
13

For the record, Redu's excellent answer without the class sugar.

See also

const foo = {
  promise: Promise.resolve(),
  log(txt) {
    this.promise.then(_ => console.log(txt));
    return this;
  },
  wait(ms) {
    this.promise = this.promise.then(_ => new Promise(v => setTimeout(v, ms)));
    return this;
  }
};

// OR
const Foo = (defaultMs = 1000) => {
  let promised = Promise.resolve();
  return {
    log(txt) {
      promised.then(_ => console.log(txt));
      return this;
    },
    wait: function(ms) {
      promised = promised.then( _=> 
        new Promise( rs => setTimeout(rs, ms || defaultMs) ) );
      return this;
    }
  };
};

foo.log("Happy").wait(1000).log("new").wait(1000).log("year");
Foo().wait(3000)
  .log(`** From Foo ;)`).log(`Happy`).wait().log("new").wait().log("year");

Comments

1

Place the call to wait inside the previous one, and as the last item, like a recursive function.

meals=['breakfast','elevenses','lunch','afternoon tea','dinner','supper'];
c=0;
wait=t=>{setTimeout(function() {
      if (c<meals.length) document.write(meals[c++],'<br>');wait(500);
    }, t);}

wait(500);

Comments

1

I was inspired by @James 's solution, which is partially wrong because the log messages are in the reverse order, but he does not use Promises. I still think that @Redu 's solution should be the accepted one (after all if you can use Promises, that is perfect), but this one is interesting too in my opinion:

const foo1 = {
    incrementalTimeout: 0,
    nextActions: [],
    log(text) {
        const textLog = () => { console.log(text); };
        if (this.incrementalTimeout == 0)
            textLog();
        else
            this.nextActions.push(textLog);
        return this;
    },
    wait(time) {
        let newObj = {...this, incrementalTimeout: this.incrementalTimeout + time, nextActions: []};
        setTimeout(() => { newObj.nextActions.forEach((action) => action()); } , newObj.incrementalTimeout);
        return newObj;
    }
}
foo1.log('breakfast').wait(1000).log('lunch').wait(3000).log('dinner');

The idea is that I do not immediately log text but I push a lambda with the console.log in an array that is going to be called after the correct timeout expires.

I run all the log and wait operations one after the other, but I keep track of the seconds to wait before executing the actions. Every time a new wait is called, the incrementalTimeout is increased by time. To keep the nextActions belonging to different time frames separated, I return a newObj every time, more or less like @James does.

Comments

0

Shorter, within Promise (not recommended).

Promise.prototype.log = function(txt) {
    return this.then(() => console.log(txt))
}

Promise.prototype.wait = function(ms) {
    return this.then(() => new Promise(res => setTimeout(res, ms)))
}

var foo = Promise.resolve()
foo.log('breakfast').wait(3000).log('lunch').wait(3000).log('dinner')

1 Comment

Very much not recommended: Promise.prototype is broken now for other users of it.
0

You can do it without promises:

const foo = {
    log(text) {
        return {...foo, start: () => {
            this.start();
            console.log(text);
        }};
    },
    wait(time) {
        return {...foo, start: () => {
            setTimeout(this.start, time);
        }};
    },
    start() {}
};
foo.log('breakfast').wait(3000).log('lunch').wait(3000).log('dinner').start();
The functions foo.log() and foo.wait() return immediately, returning a modified clone of foo. A clone is made using {...foo}, but with the start() function modified so that it calls the caller's this.start() followed by the new operation. When the chain is complete you call start() to start the actions.

2 Comments

This implicit linked list you're building is interesting - unfortunately wait runs it in the wrong order
@Bergi I agree, that is an interesting idea, I have recycled it in my answer if you want to take a look.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.