4
votes

I've written a short script using Async/Await that prints out letters one by one after short intervals. Based on what I understood to be happening, I tried rewriting the code in several ways expecting the same result, but I have been unable to make any of these alternatives work. In particular, I thought it would be straightforward to change where in the code the console.log() happens.

Here's the original working code:

const welcomeMessage = () => {
  
  const message = 'hello'
  const timer = [200,400,200,400,200,400];

// Promisify setTimeout() and feed in counter from sendMessage()
  const setTimeoutPromise = num => {
    return new Promise(resolve => {
      setTimeout(resolve, timer[num]);
    })
  };

// Async/Await with a For loop calling setTimeoutPromise()
  const sendMessage = async () => {
    for (count = 0; count < message.length; count++) {
      await setTimeoutPromise(count);
      console.log(message[count]);
    };
  };

  sendMessage();

}

welcomeMessage();

Then I tried to make a few modifications, none of which worked.

Mdofication #1: In this version, I thought I could just call and run the code in the sendMessage() function directly without needing to call it later. However, nothing happened after this modification:

async () => { //No name and removed call to sendMessage() later in code
  for (count = 0; count < message.length; count++) {
    await setTimeoutPromise(count);
    console.log(message[count]);
  };
};

const welcomeMessage = () => {
  
  const message = 'hello'
  const timer = [200,400,200,400,200,400];

// Promisify setTimeout() and feed in counter from sendMessage()
  const setTimeoutPromise = num => {
    return new Promise(resolve => {
      setTimeout(resolve, timer[num]);
    })
  };

  async () => { //No name and removed call to sendMessage() later in code
    for (count = 0; count < message.length; count++) {
      await setTimeoutPromise(count);
      console.log(message[count]);
    };
  };
}

welcomeMessage();

Modification #2: I reverted the code and then tried to move the console.log() function into the setTimeout() function thinking this would be called on every loop. Both with empty ()'s and with (resolve) being passed into setTimeout(), it only printed the first letter. With (resolve, num) it says undefined:

  const setTimeoutPromise = num => {
    return new Promise(resolve => {
      setTimeout((resolve) => {
          console.log(message[num]);
          resolve;
      }, timer[num]);
    })
  };

  const sendMessage = async () => {
    for (count = 0; count < message.length; count++) {
      await setTimeoutPromise(count);
    };
  };

const welcomeMessage = () => {
  
  const message = 'hello'
  const timer = [200,400,200,400,200,400];

  const setTimeoutPromise = num => {
    return new Promise(resolve => {
      setTimeout((resolve) => {
          console.log(message[num]);
          resolve;
      }, timer[num]);
    })
  };

  const sendMessage = async () => {
    for (count = 0; count < message.length; count++) {
      await setTimeoutPromise(count);
    };
  };

  sendMessage();

}

welcomeMessage();

Modification #3: Finally, I tried to define a function in advance to be passed into setTimeout() which would be used to handle "resolve" and console.log(). I tried a few variations and again didn't seem to be progressing through the loop as console.log() was only called once.

  // New function to handle resolve and the counter
  function newFunction(func, num) {
    console.log(message[num]);
    func;
  }

  const setTimeoutPromise = num => {
    return new Promise(resolve => {
      setTimeout(newFunction(resolve, num), timer[num]);
    })
  };

  const sendMessage = async () => {
    for (count = 0; count < message.length; count++) {
      await setTimeoutPromise(count);
    };
  };

const welcomeMessage = () => {
  
  const message = 'hello'
  const timer = [200,400,200,400,200,400];

  // New function to handle resolve and the counter
  function newFunction(func, num) {
    console.log(message[num]);
    func;
  }

  const setTimeoutPromise = num => {
    return new Promise(resolve => {
      setTimeout(newFunction(resolve, num), timer[num]);
    })
  };

  const sendMessage = async () => {
    for (count = 0; count < message.length; count++) {
      await setTimeoutPromise(count);
    };
  };
  
  sendMessage()

}

welcomeMessage();
1
func; is not a function call (compare to existing function calls like setTimeout(…) – it needs parentheses). async () => {} is a function expression that is, too, missing a call. See stackoverflow.com/questions/8228281/…. - Ry-♦
resolve; has the same problem. It should be resolve(). At setTimeout(newFunction(resolve, num), timer[num]), for the correct use of setTimeout, see stackoverflow.com/questions/7137401/…. - Ry-♦
In last code you have error you call function newFunction and pass nothing to setTimeout you need to write function that return function or wrap it inline setTimeout(() => newFunction(resolve, num), timer[num]); and you need to call that function func. - jcubic
Thanks Ry- for your replies and the useful links. I was able to get the code running and gained a bit more understanding about the implications of my code. @jcubic, thanks as well for the explanation. - UnderwaterHandshake

1 Answers

1
votes

It appears to me, that you've started working with asynchrony before you got a deep understanding of how synchronous JavaScript works. Asynchrony is hard enough on its own too, so combined to that, it made you completely confused.

Let me explain what's going on and what's wrong in your snippets.


Let's start with the working one.

That code:

const setTimeoutPromise = num => {
  return new Promise(resolve => {
    setTimeout(resolve, timer[num]);
  })
};

...creates a function named setTimeoutPromise, that:

  • takes an index (number) as its argument
  • returns a promise that:
    • after timer[num] milliseconds
    • resolves to undefined (setTimeout doesn't pass anything to its callback by default; in this case the callback is the resolve function)

The next part:

const sendMessage = async () => {
  for (count = 0; count < message.length; count++) {
    await setTimeoutPromise(count);
    console.log(message[count]);
  };
};

...defines an async function named sendMessage, that:

  • iterates over message, for each character:
    • calls setTimeoutPromise and awaits the promise it returns
    • after waiting, logs the current character to the console

Finally,

sendMessage();

...calls sendMessage, and therefore initiates the typing.


Now, let's move on to the next snippet.

This code:

async () => { //No name and removed call to sendMessage() later in code
  for (count = 0; count < message.length; count++) {
    await setTimeoutPromise(count);
    console.log(message[count]);
  };
};

...creates an async function, but it doesn't call or assign it to any variable: simply discards it.

To fix this snippet, call the function immediately by putting () after it!

const welcomeMessage = () => {
  
  const message = 'hello'
  const timer = [200,400,200,400,200,400];

// Promisify setTimeout() and feed in counter from sendMessage()
  const setTimeoutPromise = num => {
    return new Promise(resolve => {
      setTimeout(resolve, timer[num]);
    })
  };

  (async () => { //No name and removed call to sendMessage() later in code
    for (count = 0; count < message.length; count++) {
      await setTimeoutPromise(count);
      console.log(message[count]);
    };
  })(); //The () at the end calls it
}

welcomeMessage();

Problematic snippet #2

There are 2 problems with this:

const setTimeoutPromise = num => {
  return new Promise(resolve => {
    setTimeout((resolve) => { //Problem 1
        console.log(message[num]);
        resolve; //Problem 2
    }, timer[num]);
  })
};
  1. You try to take an argument named resolve from setTimeout, but as I mentioned above, it doesn't pass any.

    To solve it, remove resolve from setTimeout((resolve) => {! We already have the resolve function from the above line, because of lexical scope.

  2. You don't call resolve, that keeps the awaiting code hanging after the first letter (the promise never gets resolved).

    To fix it, put () after resolve!

const welcomeMessage = () => {
  
  const message = 'hello'
  const timer = [200,400,200,400,200,400];

  const setTimeoutPromise = num => {
    return new Promise(resolve => {
      setTimeout(() => {
          console.log(message[num]);
          resolve();
      }, timer[num]);
    })
  };

  const sendMessage = async () => {
    for (count = 0; count < message.length; count++) {
      await setTimeoutPromise(count);
    };
  };

  sendMessage();

}

welcomeMessage();

Problematic snippet #3

There are 2 problems in this code as well:

// New function to handle resolve and the counter
function newFunction(func, num) {
  console.log(message[num]);
  func; //Problem 1
}
 const setTimeoutPromise = num => {
  return new Promise(resolve => {
    setTimeout(newFunction(resolve, num), timer[num]); //Problem 2
  })
};
  1. The same as above; newFunction doesn't call resolve (named fn).

    Try to not forget the () when you intend to call a function

  2. That's the opposite of Problem 1. You immediately call newFunction (due to the parentheses after it: (resolve, num)), and pass its return value (undefined) to the setTimeout. Without Problem 1, this would result in immediately logging all letters.

    In this case, let setTimeout to call that function internally by removing (resolve, num) after it. To pass parameters to it, setTimeout accepts additional arguments, that it will hand over to its callback (in this case newFunction).

const welcomeMessage = () => {
  
  const message = 'hello'
  const timer = [200,400,200,400,200,400];

  // New function to handle resolve and the counter
  function newFunction(func, num) {
    console.log(message[num]);
    func();
  }

  const setTimeoutPromise = num => {
    return new Promise(resolve => {
      setTimeout(newFunction, timer[num], resolve, num);
    })
  };

  const sendMessage = async () => {
    for (count = 0; count < message.length; count++) {
      await setTimeoutPromise(count);
    };
  };
  
  sendMessage()

}

welcomeMessage();

All together...

It is possible to combine these fixes, to get something like:

const welcomeMessage = () => {
  
  const message = 'hello'
  const timer = [200,400,200,400,200,400];

  // New function to handle resolve and the counter
  function newFunction(func, num) {
    console.log(message[num]);
    func();
  }

  const setTimeoutPromise = num => {
    return new Promise(resolve => {
      setTimeout(newFunction, timer[num], resolve, num);
    })
  };

  (async () => {
    for (count = 0; count < message.length; count++) {
      await setTimeoutPromise(count);
    };
  })();

}

welcomeMessage();

Conclusion

Use parentheses (()) to call a function, but avoid them to use the function as an object: pass or assign it to something, get or set its properties, etc.