Jordan Savant # Software Engineer

const args = process.argv.slice(2)
if (args.length == 0) {
  console.error("Error: missing event loop arg");
  process.exit(1)
}
const which = args[0]

/*
 * Call stack pushes functions onto the stack and executes
 * the one on the end each event loop
 * So in this example the stack goes like this
 * foo1
 * foo1 console.log
 * foo1
 * foo1 bar1
 * foo1
 * foo1 baz1
 * foo1
 * - empty
 */
if (which == 1) {
  const bar1 = () => console.log('stack: bar1')
  const baz1 = () => console.log('stack: baz1')
  const foo1 = () => {
    console.log('stack: foo1') // 2
    bar1() // 3
    baz1() // 4
  }
  foo1() // 1
}


/*
 * setTimeout timers are run in an IO thread and their callbacks
 * are put into the Message Queue which only is processed after
 * the timer or IO is complet and the Call Stack is empty
 * foo2
 * foo2 console.log
 * foo2
 * foo2 setTimeout
 * foo2
 * foo2 baz2
 * foo2
 * bar2
 * bar2console.log
 * bar2
 */
if (which == 2) {
  const bar2 = () => console.log('setTimeout: bar2')
  const baz2 = () => console.log('stack: baz2')
  const foo2 = () => {
    console.log('stack: foo2') // 2
    setTimeout(bar2, 0) // 4
    baz2() // 3
  }
  foo2() // 1
}



/*
 * ES6 introduced the Job Queue allows us to process async
 * results as soon as possible
 * CS foo3
 * CS foo3 console.log
 * CS foo3
 * CS foo3 setTimeout
 * CS foo3 new Promise
 * CS foo3 new Promise console.log
 * CS foo3 new Promise resolve
 * CS foo3 new Promise
 * CS foo3
 * CS foo3 baz3
 * CS foo3
 * JQ resolve => console.log
 * MQ bar3
 */
if (which == 3) {
  const bar3 = () => console.log('setTimeout: bar3')
  const baz3 = () => console.log('stack: baz3')
  const foo3 = () => {
    console.log('stack: foo3') // 2
    setTimeout(bar3, 0) // 8
    new Promise((resolve) => { // 3
      console.log("stack: new Promise setup") // 4
      resolve('resolve from promise: should be right after baz, before bar') // 5
    }).then(resolve => console.log(`then: ${resolve}`)) // 7
    baz3() // 6
  }
  foo3() // 1
}


/*
 * process.nextTick will run a function at the end of the current
 * tick and before the next one, and before the queues are processed
 * process.nextTick // callback
 * foo4
 * foo4 console.log
 * foo4
 * foo4 setTimeout
 * foo4
 * foo4 new Promise
 * foo4 new Promise resolve
 * foo4
 * foo4 baz4
 * foo4
 * console.log("next tick") // nexTick
 * resolve => console.log // Promise resolve callback
 * bar4 // setTimeout
 */
if (which == 4) {
  process.nextTick(() => { // 1
    console.log("process.nextTick") // 7
  })
  const bar4 = () => console.log('setTimout: bar4')
  const baz4 = () => console.log('stack: baz4')
  const foo4 = () => {
    console.log("stack: foo4") // 3
    setTimeout(bar4, 0) // 9
    new Promise((resolve, reject) => { // 4
      console.log("stack: new Promise")
      resolve('res4') // 5
    }).then(resolve => console.log(`then: ${resolve}`)) // 8
    baz4() // 6
  }
  foo4() // 2
}


/*
 * setImmediate is like setTimeout 0
 */
if (which == 5) {
  process.nextTick(() => {
    console.log("process.nextTick")
  })
  // this runs in the next tick and is essentially the
  // same as setTimeout with 0
  // running this several times will show them appearing
  // back and forth in a race condition
  setImmediate(() => {
    console.log("setImmediate")
  })
  const bar5 = () => console.log('setTimeout: bar5')
  const baz5 = () => console.log('stack: baz5')
  const foo5 = () => {
    console.log("stack: foo5")
    setTimeout(bar5, 0)
    new Promise((resolve, reject) => {
      console.log("stack: new Promise")
      resolve('res5')
    }).then(resolve => console.log(`then: ${resolve}`)) // 8
    baz5()
  }
  foo5()
}