4

I have the following functions:

async typewrite(what, where){} - simulates somebody inputting text
async typeerase(where){} - similar as typewrite, but simulates somebody pressing backspace typetimer(){} - returns a promise, random interval to simulate typing speed.

Basically, what I want to do is after the typewrite function finishes, I want to erase the text after a small delay. I tried artificially delaying it with a setTimeout, but that does not seem to work.


The following code works as expected

typewrite('Hello world!', target).then(erase => typeerase(target));

let typetimer = (fmin, fmax) => ( 
  new Promise(res => setTimeout(res, Math.floor(Math.random() * (fmax - fmin + 1)) + fmin))
); 

const textarea = document.getElementById('textarea');

async function typewrite(what, where){
  let current_text = '';
  const typespeed_min = 50;
  const typespeed_max = 150;
  
  for (let i = 0; i < what.length; i++){
    await typetimer(typespeed_min, typespeed_max);
    current_text += what.charAt(i);
    where.innerHTML = current_text;
  }
}


async function typeerase(where){
  const erasespeed_min = 30;
  const erasespeed_max = 70;
  let current_text = where.textContent;
  
  while (current_text.length > 0){
    await typetimer(erasespeed_min, erasespeed_max)
    current_text = current_text.substring(0, current_text.length - 1);
    where.innerHTML = current_text;
  }  
}

typewrite('Hello, I am an asynchronous function!', textarea)
  .then(erase => typeerase(textarea));
html {
  font-family: 'Courier New';
  font-weight: bold;
  font-size: 15pt;
}
<p id="textarea"></p>

However, applying artificial setTimeout() delay does not seem to affect it at all.

typewrite('Hello world!', target).then(erase => setTimeout(typeerase(target), 50000));

let typetimer = (fmin, fmax) => ( 
  new Promise(res => setTimeout(res, Math.floor(Math.random() * (fmax - fmin + 1)) + fmin))
); 

const textarea = document.getElementById('textarea');

async function typewrite(what, where){
  let current_text = '';
  const typespeed_min = 50;
  const typespeed_max = 150;
  
  for (let i = 0; i < what.length; i++){
    await typetimer(typespeed_min, typespeed_max);
    current_text += what.charAt(i);
    where.innerHTML = current_text;
  }
}


async function typeerase(where){
  const erasespeed_min = 30;
  const erasespeed_max = 150;
  let current_text = where.textContent;
  
  while (current_text.length > 0){
    await typetimer(erasespeed_min, erasespeed_max)
    current_text = current_text.substring(0, current_text.length - 1);
    where.innerHTML = current_text;
  }  
}

typewrite('Hello, I am an asynchronous function!', textarea)
  .then(erase => setTimeout(typeerase(textarea), 100000000));
html {
  font-family: 'Courier New';
  font-weight: bold;
  font-size: 15pt;
}
<p id="textarea"></p>

It appears I'm almost missing something very basic / fundamental here, but I just can't seem to realise what am I doing wrong here.

Samuel Hulla
  • 6,617
  • 7
  • 36
  • 70
  • 4
    `setTimeout` accepts a *function* as a parameter. With `setTimeout(typeerase(target)`, you're not passing a function, you're *invoking* the function immediately, and passing its return value. – CertainPerformance Jan 25 '19 at 22:51
  • @CertainPerformance so if I get it right, i should basically create an extra layer of a Promise, something akin to a `delay` function that returns a promise for a timeout and only then invoke the `typeerase()` function, or is there perhaps a more elegant way of achieving this? – Samuel Hulla Jan 25 '19 at 22:55

2 Answers2

8

Short answer: setTimeout doesn't work like that. You should be able to do this instead:

typewrite('Hello world!', target)
    .then(erase => setTimeout(() => typeerase(target), 50000));

Long answer: You should probably use a helper method that wraps setTimeout to produce a desired delay (Noitidart's answer shows an example of this)...

typewrite('Hello world!', target)
    .then(() => delay(50000))
    .then(() => typeerase(target));

... and then use await

await typewrite('Hello world!', target);
await delay(50000);
await typeerase(target);
Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315
  • Thanks for the sportsmanship bro! :) – Noitidart Jan 25 '19 at 22:59
  • 1
    :-) You bet. Good team-work. I'm still trying to figure out why somebody down-voted your answer. – StriplingWarrior Jan 25 '19 at 23:00
  • There was an error in his original answer *(wasn't me who downvoted though)*. – Samuel Hulla Jan 25 '19 at 23:00
  • 1
    I really appreciate that comment about the downvvote, and you were probably the upvote to fix it :) Thank you! – Noitidart Jan 25 '19 at 23:01
  • @StriplingWarrior one quick question, I got slightly confused with the final part of your answer *"and then use `await`"*. What is the reasoning here for the additional `await`? Wouldn't the delay work by simply applying it to a `.then( () => delay(ms));`? – Samuel Hulla Jan 25 '19 at 23:19
  • Here's the example without the additional awaits https://codepen.io/Rawrplus/pen/PVZePd – Samuel Hulla Jan 25 '19 at 23:20
  • @Rawrplus: Yes, all three code blocks in my answer should work correctly. I'm just showing the progression which I would personally recommend, from callbacks through Promises into `async/await` syntax, which simplifies asynchronous patterns quite a bit. `await` removes the need for `.then()`-chaining. – StriplingWarrior Jan 28 '19 at 15:57
3
function delay(ms, resolveWith) {
    return new Promise(resolve=>setTimeout(resolve, ms, resolveWith));
}

typewrite('Hello world!', target)
  .then(erase => delay(5000, erase))
  .then(erase => typeerase(target));
Noitidart
  • 35,443
  • 37
  • 154
  • 323
  • 1
    um... why not just `setTimeout(resolve, ms, ...args)` ? Also `resolve` only takes one argument, so spreading is senseless. – Jonas Wilms Jan 25 '19 at 23:04
  • Perhaps a quick question, what is the reasoning behind `.bind(null, ...args)` ? There does not appear to be any use for `this` keyword. – Samuel Hulla Jan 25 '19 at 23:05
  • @JonasWilms I think the `typewrite` function he has resolves with something called `erase` in the argument. So if he wants to use that in the `.then` after the delay, he needs a way to get the `erase` argument to the next `.then` callback. I actually think `resolve` can only resolve with one argument, so maybe I should change `...args` to just `arg`. – Noitidart Jan 25 '19 at 23:06
  • Oh @JonasWilms! I didn't know `setTimeout` took more args then the first two, thats really cool! I learned something by answering a question! Updated. – Noitidart Jan 25 '19 at 23:29