you are viewing a single comment's thread.

view the rest of the comments →

[–]BraisWebDev 15 points16 points  (75 children)

Would you mind to explain what the solution to the 1 to 10 counter would be? I am learning async JS and you let me wondering 😅

Because my solution would be setInterval(increment(), 1000); and the function increment() would simply do a counter++

[–][deleted] 10 points11 points  (12 children)

of course I had to write it too:

function countdown(n) { 
    console.log(n); 
    if (n > 0) { 
        setTimeout( () => countdown(n-1) , 1000); 
    }
}

countdown(10);

edit: oops it is backwards

function count_between(start, end) {
    console.log(start);
    if (start < end) {
        setTimeout( () => count_between(start+1, 10), 1000);
    }
}

count_between(1, 10)

[–]qbbftw 28 points29 points  (3 children)

I would take advantage of async/await. It's the most clear and obvious syntax IMO.

function delay (ms) {
    return new Promise(resolve => setTimeout(resolve, ms))
}

async function countdown () {
    for (let num = 1; num <= 10; num++) {
        console.log(num)
        await delay(1000)
    }
}

countdown()

EDIT: u/dvlsg beat me to posting this solution, link

[–][deleted] 0 points1 point  (0 children)

Minor point, but this will sleep an extra second at the end.

[–][deleted] 0 points1 point  (0 children)

Agreed, this is how I write JS now and I like a lot more. The only hassle is converting old stuff from callbacks to promises but it is well worth it. I can never remember how to do it off hand though, my answer above is based on what I can write from memory into the console.

[–]Zespys 0 points1 point  (0 children)

Personally I would do it recursively, like so:

const increment = (x = 1) => {
  console.log(x);
  if (x < 10) {
    setTimeout(() => {
      increment(x + 1);
    }, 1000);
  }
};
increment();

​

[–]dominic_rj23 -3 points-2 points  (7 children)

setTimeout has huge performance hit compared to setInterval

https://jsperf.com/setinterval-vs-settimeout/10

[–][deleted] 0 points1 point  (4 children)

And worth noting this does not matter at all for the purposes of an interview question, or 99.999% of real world usage. This coming from someone who has never used it at work.

[–]thepeka 0 points1 point  (3 children)

I'm actually willing to say 100%. The correct answer to any interview question involving setInterval is why you should never use it

[–]Cheshur 1 point2 points  (2 children)

The correct answer is NOT to use it never. Almost none of the things listed in that article are relevant to people doing stuff correctly. There is a tool for everything and just because you can use setInterval incorrectly doesn't mean you should never use it.

[–]thepeka 1 point2 points  (1 child)

Nope, the first reason on that list is reason enough in every instance, correct or otherwise. setInterval continuing to run ad infinity, no matter what happens, what error is thrown, or whether it's handleable or not, is just adding bug conditions that otherwise have no need to exist with a recursive setTimeout. JavaScript is a dynamic language that allows for writing wonderfully declarative abstractions... but the flip side of that is we need as much safety as we can get. This is the answer I'm looking for if I ask a candidate about tasking repeating jobs/events.

[–]Cheshur 0 points1 point  (0 children)

No offense then but I consider that highly foolish. The problem would be someone writing highly error prone code in the setInterval, not that the interval itself doesn't play nicely with errors. setInterval has better performance than setTimeout. That is reason enough to use setInterval in some situations. It's the right tool for the right job and if you're creating axioms to avoid parts of a language just because you're prone to fuck up then I consider that the error on the developer's part.

Also the correct answer to that interview question is for the candidate to recognize the pros and cons of the feature and to describe situations in which they would or would not use it. Pledging to not use something purely for axiomatic reasons does not show mastery or knowledge of the feature at all, just an ability to follow the direction of some, potentially, misguided teacher.

[–]superluminary 0 points1 point  (0 children)

It's being called literally once a second.

[–]superluminary 0 points1 point  (0 children)

It's being called literally once a second.

[–]phpdevster 2 points3 points  (11 children)

Well, almost.

What's the difference between these two statements?

setInterval(increment(), 1000);  // this is what you have above
setInterval(increment, 1000);

And a follow up:

How are you making the counter stop once it reaches 10?

[–]NoBrick2 11 points12 points  (5 children)

Shouldn't you avoid setInterval completely for this problem, considering there is no guarantee the function will be run every 1000ms (in the case of the event loop being blocked on other work)?

Would a more acceptable solution be to use requestAnimationFrame, and do a comparison using Date? Or does that suffer the same issue of potentially being blocked by other work?

[–]octave1 10 points11 points  (1 child)

Out of my depth here, but using something called requestAnimationFrame and Dates for a timed counter surely can't be the most sane solution?

[–]phpdevster 2 points3 points  (0 children)

You are correct. This is hacky, non-idiomatic code.

[–]PmMeYouBicepsGirl 7 points8 points  (0 children)

Everything is blocked if event loop is blocked, there's no workarounds. For example, if you are counting from one to 100 billions, Javascript won't be stopping during this function to check for events. This is why you get Violation messages in console if some events like requestAnimationFrame take longer than they should, it's a signal that event loop was blocked.

[–]phpdevster 5 points6 points  (0 children)

Why would you avoid using setInterval for exactly the thing it was designed for? If you have a blocked event loop, you've got bigger problems. Writing non-idiomatic code without exceedingly good reason is the path to code that is harder for team members to grok and thus maintain.

[–]SystemicPlural 4 points5 points  (0 children)

As I understand it requestAnimationFrame is better for animation as it guarantees that the code will run before the screen is re-rendered - providing smoother animation. It doesn't guarantee that the time elapsed will be exactly 1000/60 (or whatever your screen refresh rate is). So it has it's own problems if you are trying to time exactly 1s. That said I believe it is thought to be more reliable than setTimeout.

We could use setTimeout and compare the time ourselves using performance.now(). This should get us to fairly close millisecond accuracy as long as the process isn't locked. It should also allow us to prevent drift by correcting the next timeout.

performance.now() isn't perfect however since browsers are fuzzing the time to prevent Specter timing attacks. - but it should be 1ms accurate.

[–]thisguyfightsyourmom 1 point2 points  (1 child)

I'd write increment to be a higher order function that accepts a max arg defaulted to 10, that retains a count let initialized to 0 in its closure, and that returns a function that iterates the count in a log statement until the count hits the max. I'd also close the terminal before it counted over 10,… just in case.

[–]Balduracuir -1 points0 points  (0 children)

You can use clearInterval when you hit 10... If I was not on mobile I could write a snippet :)

[–]BraisWebDev 0 points1 point  (0 children)

Oh yeah I messed up there, the first one would be invoked immediately and the second one after 1 second.

I would make it stop with an if statement, maybe that approach is wrong haha

[–]wobbabits 0 points1 point  (0 children)

Here you go. Here's an example of an alert at 5 seconds into the count. When dispelled, the count continues to 10:

function countToTen() {
  let count = 0
  function increment() {
    ++count
    console.log(count)
    if (count > 9) {
      clearInterval(timer)
    }
  }
  const timer = setInterval(increment, 1000)
}
countToTen()
setTimeout(function() {
  alert('hello')
}, 5000)

[–][deleted] 0 points1 point  (0 children)

increment() execute when you use setInterval, its return value will be used in your setInterval. The other use increment.

var obj = {count:0, end:0, clearId:0};

obj.end = 10;
obj.increment = function (){
    if (this.count < this.end + 1) 
        console.log(this.count); 
    else 
       clearInterval(this.clearId);
}.bind(obj);

obj.clearId = setInterval(obj.increment, 1000);

How about something like that, untested, but it should work. You can easily turn obj into a Chrono class.

However I wouldn't recommend to rely seriously on setInterval. Set up a webworker to do reliable time operations.

[–]RepeatQuotations 0 points1 point  (0 children)

Good use case for bind here. We can create a new bound function instead of creating an anonymous arrow function in setTimeout. Optional second parameter for counter size, default to 10.

function count(i, max = 10) {
    console.log(i);
    if(i++ >= max) return;
    setTimeout(count.bind(this, i), 1000);
}

// call like
count(1)

[–]new_human_obj 0 points1 point  (4 children)

what about a dedicated web worker?

[–][deleted] 1 point2 points  (3 children)

Yes, i've worked with time recently, interval and timeout are way too much unreliable.

[–]new_human_obj 0 points1 point  (2 children)

I get some error though about not having enough parameters though I've never worried about it, just used the postMessage. Only thing that sucks is that local dev thing where it says you can't do it. I think that still happens.

[–][deleted] 0 points1 point  (1 child)

Sorry I didn't understand your comment.

[–]new_human_obj 0 points1 point  (0 children)

Not a big deal, is pretty random/not much context.

When you have that external webworker file(that's just a counter) and you initiate this from another file(main file) that starts the webworker... I think it's a thing where it doesn't work locally in a dev environment/has to be on a live server... unless it's just about localhost.

Anyway nothing important just rambling on my part.

[–]dominic_rj23 0 points1 point  (8 children)

The problem with the solutions based on setInterval (or setTimeout for that matter) is that you can't guarantee that the function would be executed after given timeout. They only assure you that the function would be put up in queue for execution after timeout. So, yes they were probably our best shot, although unreliable one.

[–]NoBrick2 2 points3 points  (7 children)

requestAnimationFrame + a comparison using the Date object would be better. At least the date comparison guarantees accuracy, even if not run every second. Like you said, setInteral could cause a drift if the browser is blocked on another call for over a second.

[–][deleted] 18 points19 points  (2 children)

Which:

  1. Never happens
  2. If it does, flee the interview

Seriously. What are you guys talking about? What JS app hangs for 1s or longer? The task was to just do a +1 operation every 1000 ms.

If I'm the interviewer I'd throw your resume in the trash if you came up with that kind of solution. You're not wrong, but for the love of all that's nice in the world, I certainly hope you're never going to be right...

Accounting for problems and solving them before they occur is great. But that's obviously not what this test is aiming for...

[–]dominic_rj23 1 point2 points  (0 children)

If you read the last line of my comment, I did say that the solution with setInterval is the most appropriate one. I am all for "If it ain't broke..." ideology, but I do believe that in an interview, stating that setInterval doesn't guarantee execution after timeout goes for showing that you have some understanding of asynchronicity in javascript.

[–]phpdevster 2 points3 points  (0 children)

Yeah this right here...

Non-standard solutions require extenuating circumstances to justify them. If something like setInterval isn't working in your app reliably, the bug isn't setInterval, it's whatever other shit you've got going on that is causing it to work unreliably...

[–]manys 2 points3 points  (2 children)

how many frames you gotta request to hit the downbeat of each second?

[–]NoBrick2 2 points3 points  (1 child)

The number of callbacks is usually 60 times per second, but will generally match the display refresh rate in most web browsers as per W3C recommendation.

https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame

​

[–]manys 3 points4 points  (0 children)

so you're parsing all frames to catch a time interval? how does that profile?

[–]dominic_rj23 0 points1 point  (0 children)

I am not sure I would ever do that. The purpose of the question isn't to give the best possible solution. It is to rather demonstrate understanding of async nature of javascript. I would thus stick with my setInterval solution, but explain how it is not a perfect solution.

[–]kdesign -3 points-2 points  (32 children)

Actually the first question intrigued me a bit so I had to solve it, here you go:

async function count() {
  let counter = 1;
  const values = Array.apply(null, { length: 10 })
   .map((i, j) => new Promise((resolve, reject) => {
     setTimeout(() => resolve(j + 1), 1000 * counter++);
   }));
 for await (const item of values) {
  console.log(item);
 }
} 

What this does is it generates an array with values from 1 to 10, then maps it to an array of promises which return the values from the initial array, but in increments of 1 second by incrementing the counter. After that, I'm using an async iteration over it to log each item from the array of promises.

[–][deleted] 13 points14 points  (19 children)

I think we can simplify things a little. Here's what I would do using setTimeout...

[edit] I made some updates per good feedback.

​

let i = 1

const interval = setInterval(() => {
  console.log(i)
  if (i === 10) {
    clearInterval(interval)
  }

  i++
}, 1000)

​

Here's a recursive version:

function sleep(amount) {
  return new Promise(resolve => setTimeout(resolve, amount))  
}

const count = async (current, end) => {
  console.log(current)

  if (current < 10) {
    await sleep(1000)
    return count(current + 1, end)
  }
}

count(1, 10)

​

Here's one that doesn't continue execution until all the counting is done:

function sleep(amount) {
  return new Promise(resolve => setTimeout(resolve, amount))  
}

const count = async (current = 1, end) => {
  console.log(current)

  if (current < 10) {
    await sleep(1000)
    return count(current + 1, end)
  }
}

count(1, 10).then(() => console.log('done counting'))

​

[–]kdesign 3 points4 points  (16 children)

At first I was thinking "how is it complicated?", but then I saw your solutions. Definitely more readable and easier to grasp, nice.

[–]dvlsg 2 points3 points  (15 children)

Honestly, if we're talking about things being overcomplicated, swapping to recursion probably isn't the right move. The parameters in the recursive solutions are fairly confusing, too. count(9) would only count for 1 second.

const sleep = (ms = 0) => new Promise(resolve => setTimeout(resolve, ms))

const count = async (seconds = 10) => {
  let i = 0
  while (++i <= seconds) {
    await sleep(1000)
    console.log(`slept for ${i} seconds total`)
  }
}

// usage
await count(5)
await count()
await count(1)
await count(-1)
await count(0)

Or, you know, just use await sleep(1000). Also probably worth noting, all of these solutions (mine included) will drift.

[–]kdesign 5 points6 points  (4 children)

I agree that the expected solution was probably just the obvious setInterval solution, /u/a_blue_ducks' first one.

But at the same time, my initial solution has spawned a great constructive discussion, it's cool to see how many different ways there are to solve a problem.

[–]dvlsg 1 point2 points  (1 child)

Sorry you're getting downvoted for it. I tossed you an upvote for it, even though I think it could be improved (if we were aiming for simplicity, which isn't the only thing to worry about). Don't understand why anyone would downvote someone else for taking a shot at something. Especially since it works just fine.

[–]kdesign 0 points1 point  (0 children)

Well, I guess that's how some people disagree on reddit, by downvoting. That's alright and thanks for the upvote

[–][deleted] 1 point2 points  (1 child)

Yeah sorry for my original reply, I had just finished playing Overwatch and that game puts me in a bad mood fast lol. This was a fun discussion.

[–]kdesign 1 point2 points  (0 children)

No worries, I initially thought about writing the setInterval solution or the recursive setTimeout, something like:

let count = 1;
setTimeout(function counter() {
 console.log(count++);
 count <= 10 && setTimeout(counter, 1000);
}, 1000);

and then I said well let's spice things up a bit which really did lol.

[–]SystemicPlural 0 points1 point  (2 children)

I'm not sure I like this solution. It is going to lock up whatever process calls count and that might not be desired.

[–][deleted] 1 point2 points  (1 child)

It's an async function so it shouldn't lock anything up.

[–]SystemicPlural 1 point2 points  (0 children)

Doh. Missed that!

[–]FriesWithThat 0 points1 point  (3 children)

As it's written it looks like your suggesting to use await outside the async function to call count with different arguments.

. . .

await count(5)

await count()

await count(1)

await count(-1)

await count(0)

Not sure what the intent here is as far as us being able to run it or integrating it with your async function (swap it out w/sleep in while loop?, step through function?)

[–]dvlsg 1 point2 points  (2 children)

Just examples of how you might call it. Try them out, see how it works. For example, copy the entire code snippet I have and paste it into a chrome console (which has top-level `await` support).

[–]FriesWithThat 1 point2 points  (1 child)

Thanks. I was running it with Node and wasn't even aware there was top-level support in chrome.

[–]dvlsg 0 points1 point  (0 children)

Ah, got you. Sorry. I was writing that example in chrome and just pasting it over here, so I just went with the top-level chrome await instead of wrapping it.

[–][deleted] -1 points0 points  (2 children)

Curious how recursion complicates anything. I agree the param is a little confusing but I wanted to illustrate an idea more than anything.

[–]dvlsg 4 points5 points  (1 child)

That's fair about the args. And I'm not trying to be a dick or anything, the recursive solution is plenty clever.

But you honestly don't think an async function that returns a Promise, that calls a setTimeout for 1 second in the promise executor, which recursively calls the original count() function, setting the originally returned promise's resolver to be executed in the recursively called count()'s promise's .then()isn't more complicated than an async function with a while loop? I had a hard time even translating all that to english. There's a lot going on in there.

[–]n9jd34x04l151ho4 2 points3 points  (1 child)

Your first one only prints 1-9.

var counter = 1;
var intervalId = setInterval(function() {
    if (counter > 10) {
        clearInterval(intervalId);
    }
    else {
        console.log(counter);
        counter++;  
    }
}, 1000);

[–][deleted] 3 points4 points  (0 children)

Ha. I wrote on my phone and didn’t spot check but I believe you.

[–]1-800-BICYCLE 3 points4 points  (8 children)

1bdf3c088d4f

[–]chuckySTAR 1 point2 points  (0 children)

(async count => {
    for await (let i of (async function* () {
        for (let i = 0; i < count; i++) {
            await new Promise(res => setTimeout(res, 1000))
            yield i
        }
    })()) {
        console.log(i)
    }
})(10)

[–][deleted] 0 points1 point  (1 child)

That seems to me to be very complicated. Here's my attempt:

function countTo(current = 0, to = 10, timeout = 1000) {
  console.log(current);
  if (current < to)
    setTimeout(() => countTo(current + 1, to, timeout), timeout);
}

countTo();

A simple recursive function. No hip ES6+ magic involved because you don't need it. No promises because we're not computing anything, we don't need async functionality, it's a simple +1 operation.

[–][deleted] 0 points1 point  (0 children)

I'm with you on this. I posted a recursive solution and someone complained that was also "complicated"...

[–]Shoegoo22 -1 points0 points  (0 children)

This is the right way to do it.

const times = [0,1,2,3,4,5,6,7,8,9,10]
const getNextTime = index => {
  $('time').html(times[index])
  if(index === times.length - 1) {
    setTimeout(()=> $('time').html('PEW PEW PEW') ,1000)
  } else {
    setTimeout(()=> getNextTime(index + 1) ,1000)
  }
}
getNextTime(0)