( บันทึกกันลืม ) การทำงานของ NodeJS


NodeJS

  • ใช้ Google V8 Engine ในการ compile Javascript ไปเป็น Byte code
  • เมื่อ code เรา ถูก compile จะถูกเก็บไว้ใน memory ต่อให้ลบ ไฟล์นั้นๆ ออกไป ก็ยังสามารถทำงานได้
  • มีการทำงาน แบบ Single-Thread ( 1 Thread : 1 Process ) ไม่ใช่ Multi-Thread แต่เพราะ การทำงานที่เป็น Non-Blocking I/O , Event Loop ของมันทำให้เข้าใจว่าเป็นการทำงานแบบ Muti-Thread Processing

image-1 รูปจาก https://ima8.me/2015/01/12/node-js-say-hi-what-is-it/

Blocking I/O

เป็นการทำงานที่เกิดการรอเกิดขึ้น ไม่ว่าจะมีการทำงานพร้อมกันหลายๆงาน ยังไงงานก็ถูกกองไปอยู่ที่เดียว รองานเก่า จะทำเสร็จและตอบกลับมา

ข้อดีของ Blocking I/O

  • เข้าใจง่าย
  • มีการทำงานที่เป็น ลำดับ

ข้อเสีย Blocking I/O

  • สิ้นเปลืองทรัพยากร เพราะอาจจะมี Thread ตัวอื่นที่ถูกสร้างไว้ แต่ไม่ถูกใช้งานจำนวนมาก

image-2

รูปจาก https://webapplog.com/you-dont-know-node/threading_java/


Non-Blocking I/O

เป็นการทำงานอะไร สักอย่างโดนที่ไม่ต้องรอ การทำงานของงานก่อนหน้าทำเสร็จ ต่อให้มีการทำงานหลายๆงานพร้อมกัน ก็จะมีการแบ่งงานของ Thread เกิดขึ้น ไม่ได้ไปกองอยู่ที่ๆเดียวกัน

ข้อดีของ Non-Blocking I/O

  • เร็ว
  • ไม่เกิดการรอ การทำงานของงานที่ถูกสั่งมา

ข้อเสีย Non-Blocking I/O

  • มีการทำงานที่ไม่เป็นไป ตามลำดับ

image-3


Event Loop Model

เป็นการโมเดลการ ทำงานแบบ Asynchronous จะมีขั้นตอนประมาณนี้

  • เมื่อมี Request เข้ามา ระบบ จะไปหยิบ Thread มารับงานที่เราสั่งมา
  • จากนั้น Thread จะนับงานไปทำ อะไรสักอย่างที่เราสั่ง ไปทำงาน
  • จากนั้น Thread นั้นจะไปรับงานถัดไป โดยที่ไม่รอ งาน เดิมเสร็จ

image-4


ก่อนจะไปเรื่องการทำงาน Async/Await จะมีการทำให้ Function ทำงาน แบบ Asynchronous อยู่อีก 2 แบบ คือ

  • Callback Function

เป็นการ เขียน Function ซ้อน Function เพื่อรอการทำงาน จะเป็นประมาณนี้

function A(param ,function(err,resA){
    function B(resA,function(err,resB)){
        // ทำไรสักอย่าง
    })
)}

โดยปกติ จะให้เป็น parameter ตัวแรก เป็น error แล้วเช็คว่า ถ้า error เป็น null แสดงว่าไม่ error

  • Promise

เกิดขึ้นจาก การทำ callback function ที่มีความซับซ้อนมากๆ ( callback hell )

ประกอบไปด้วย 3 สถานะ

  • Pending เป็นสถานะแรกสุดเมื่อมีการเริ่มเรียก
  • Resolved เป็นสถานะที่ได้หลังจากการเรียกที่ไม่ error และจะอยู่ใน .then()
  • Rejected เป็นสถานะที่ถูกตอบออกมาหลังจากเรียกแล้วมี error และจะอยู่ใน .catch()

จะเป็นประมาณนี้

const datas = new Promise((resolved,reject)=> {// ทำอะไรสักอย่าง} )
.then(data=> // {resolved})
.catch(err=> // {rejected})

การทำงาน Async/Await

ด้วยความที่ Node มีการทำงานแบบ Non-Blocking I/O หรือ Asynchronous แต่การใช้งาน Function บางอย่างอาจจะเป็น Synchronous เช่น การทำอะไรกับฐานข้อมูล หรือทำอะไรที่ต้องรอให้คำสั่งนั้นทำงานเสร็จถึงจะไปทำอันถัดไป

จึงได้มีการนำ การ Async/Await มาทำในส่วนนี้

  • ตัวอย่าง การใช้ Axios
let datas = axios.get('xxx.com')

console.log(datas)

สิ่งที่ axios ส่งออกมาคือค่า Promise Object เพราะ ภายใน axios เป็นการทำงาน แบบ Asynchronous ซึ่งสามารถรอรับค่าจากที่ axios ส่งมาได้อีกทางด้วยการ ใน callback

axios.get('xxx.com').then(data=>console.log(data))
  • ก็จะเป็นการรอรับ ค่าที่ axios ตอบกลับมาได้

แล้วถ้าในกรณี ที่มีการ เลือกให้ Axios หลายที่ ก็จะต้องทำประมาณนี้

//งาน A
axios.get('xxx.com').then(xdata=>console.log(xdata))
//งาน B
axios.get('yyy.com').then(ydata=>console.log(ydata))

ที่นี้ แล้วถ้าเป็นในกรณี ที่เราต้องการที่จะให้ งาน A ทำงานเสร็จก่อนแล้วจึงไปทำงาน B ละ

ก็จะได้ประมาณนี้

// งาน A
axios.get('xxx.com').then(xdata=>{
    // งาน B
    axios.get('yyy.com').then(ydata=>{
        console.log(ydata)
    })
})

ที่นี้ การทำงานของเราก็จะเป็นทำงานแบบเรียงตามลำดับได้แล้ว แต่แลกมากับปัญหา callback hell ลองนึกสภาพที่ มีการทำคำสั่งแบบนี้ สัก 10 ขั้นละ นอกจากมั่วแล้ว ยังอ่านยาก อีก จึงนำมาซึ่ง Async/Await

  • Async/Await

เกิดจากการข้อเสีย ที่ Promise พอลำดับเริ่มเยอะขึ้น มีการใช้ .then() ไปหลายชั้น คล้าย callback hell แต่ในที่นี้เรียกว่า promise hell

การใช้งาน Async/Await

  • กำหนด Async ไว้หน้า function เพื่อบอกว่า function นี้มีการทำงานแบบ Asynchronous
  • ใช้ Await เพื่อบอกให้รอการทำงานจาก function นี้ก่อน แล้วจึงไปทำ function ถัดไป

เช่น

// แบบปกติ
function A(){
    console.log("A")
}

// แบบ async function
async function A(){
    console.log("A")
}

ที่นี้ เรียกใช้ function ด้วย Await


async function main (){
    let datas = await A();
    console.log(data) // A
}

ยกตัวอย่างจาก axios ด้านบน

// แบบปกติ
axios.get('xxx.com').then(xdata=>{
    // งาน B
    axios.get('yyy.com').then(ydata=>{
        console.log(ydata)
    })
})

/// แบบ async function 
async function call(){
    let callA = await axios.get('xxx.com')
    let callB = await axios.get('yyy.com')
}

เพียงเท่านี้ ก็จะสามารถ ทำงานเป็นลำดับ และอ่านง่ายขึ้นด้วย


https://v8.dev/