javascriptasync-awaitasynchronous-javascript

Asynchronous javascript: How to run a function during the execution of another function


Im trying to wrap my head around asynchronous javascript. So I wrote a function to test some things out. What I'm trying to do is write a scenario where I'm:

  1. Putting Popcorn in the microwave
  2. During the execution of cooking popcorn I'm pouring drinks (I don't want to have to wait for the popcorn to finish cooking before pouring the drinks)
  3. After the popcorn is finished cooking I sprinkle some salt over it (So can't execute before or during the cookPopcorn() function)
  4. The movie can begin once both the popcorn has been salted and the drinks are ready

My Question

As can be seen in the execution results, listed below, the cookPopcorn() function will finish executing before makingDrinks() is started. I don't want to have to wait for the popcorn to finish cooking before making my drinks. Is it even possible in JS to run makingDrinks() during the execution of cookPopcorn() or am I misunderstanding the use case of async/await?

MovieNight.js

function wait(ms) {
  let start = Date.now(),
    now = start;
  while (now - start < ms) {
    now = Date.now();
  }
}

async function cookPopcorn() {
  console.log("Cooking popcorn: a");
  wait(2000);
  console.log("Cooking popcorn: b");
  wait(2000);
  console.log("Cooking popcorn: c");
  wait(2000);
  return "unsalted";
}

async function makingDrinks() {
  console.log("Making drinks");
  wait(2000);
  return "Drinks finished";
}

async function saltPopcorn(popcorn) {
  if (popcorn.includes("unsalted")) {
    console.log("sprinkling salt over popcorn");
    wait(4000);
    return "salted popcorn";
  } 
}

function startMovie(popcorn, drinks) {
  console.log(`We have ${popcorn} and ${drinks}, so let's start movie...`);
}

async function setupMovieNight() {
  let popcorn = await cookPopcorn(); // starts cooking the popcorn
  let drinks = makingDrinks(); // Pour the drinks while the popcorn is busy cooking
  let saltedPopcorn = await saltPopcorn(popcorn); // Salt the popcorn only after it is finished cooking

  startMovie(saltedPopcorn, drinks); // start the movie only when the popcorn has been salted and the drinks are ready
}

setupMovieNight();

In my result cooking a,b,c finish before starting to make drinks. I would like to make drinks in-between cooking a,b or c instead of waiting for the popcorn to finish cooking.

Result

Cooking popcorn: a
Cooking popcorn: b
Cooking popcorn: c
Making drinks
sprinkling salt over popcorn
We have salted popcorn and [object > Promise], so let's start movie...


Solution

  • Your "wait" function is not asynchronous.

    It just runs a loop setting now = Date.now() repeatedly as fast as it can, blocking anything else from happening, for 2 seconds (or whatever).

    You could reimplement it using a Promise with setTimeout:

    async function wait (ms) {
      return new Promise((resolve) => {
        console.log(`waiting ${ms}`);
    
        // call resolve after the time has elapsed
        setTimeout(() => {
          console.log('done');
          resolve();
        }, ms);
      })
    }
    
    async function go () {
      console.log('start popcorn a');
      await wait(2000);
      console.log('start popcorn b');
      await wait(2000);
      console.log('start popcorn c');
      await wait(2000);
      console.log('done', Date.now());
    }
    
    go();

    You could of course just have cookPopcorn do this.

    async function cookPopcorn () {
      return new Promise((resolve) => {
        setTimeout(() => resolve('yay popcorn!'), 2000);
      })
    }
    
    function noAwait() {
      // notice that "do other stuff" is logged before "yay popcorn"
      // because we're not awaiting cookPopcorn
      console.log('do stuff, no await.');
      cookPopcorn().then(result => console.log(result));
      console.log('do other stuff, no await.');
    }
    
    async function withAwait() {
      // logs in order because we await the popcorn before moving on.
      console.log('do stuff');
      const result = await cookPopcorn();
      console.log(result);
      console.log('do other stuff');
    }
    <button onClick="noAwait()">No Await</button>
    <button onClick="withAwait()">With Await</button>