Skip to content

ใช้งาน Promise ใน Node.js ด้วย Bluebird

ทำไมควรใช้ Promise? ทำไมถึงควรใช้ Bluebird?

โพสต์ที่แล้วผมแนะนำ lib สำหรับจัดการ control flow ของ Node.js ไปคือ Async และ Bluebird ซึ่ง Async ทุกคนที่เขียน Node.js คงคุ้นเคยกันดีเพราะเป็นสไตล์ callback pattern แต่ Bluebird อาจจะยังไม่คุ้นเคยกันเพราะใช้ Promise pattern หรืออาจจะใช้กันเป็นปกติอยู่แล้วก็ไม่รู้ แต่ผมไม่สนใจครับ โพสต์นี้ผมจะมาสอนจระเข้ว่ายน้ำ (นี่แน่ะ!)

Promise ที่เป็น built-in ฟีเจอร์ของ ES6 ถึงจะสะดวกแต่ส่วนตัวผมมองว่ามันก็ยังไม่ค่อยสะดวกเท่าไหร่ถ้ามีการใช้งานในรูปแบบที่ซับซ้อนขึ้น เช่น การจัดการกับ Promise array หรือ Promise Map เพราะ class Promise ของ ES6 เองเป็น class พื้นฐานฟีเจอร์จึงน้อย Bluebird เลยสะดวกกว่า

ถ้ายังนึกภาพไม่ออกว่าสะดวกขึ้นยังไงให้นึกถึงการใช้งาน jQuery เปรียบเทียบกับ Pure JavaScript ครับ สมมุติว่าจะเรียกใช้งาน AJAX ก็ได้เอ้า Bluebird มันทำให้ง่ายขึ้นแบบนั้นนั่นแหละครับ

โพสต์นี้ผมขอยกตัวอย่างเป็น Node.js แล้วกันครับ ก่อนอื่น install แล้ว require เข้ามาใช้งานซะ

เสร็จแล้วครับเริ่มใช้ได้เลย  … เดี๋ยวๆๆๆ แล้วมันใช้ยังไงล่ะ?

ความเก่งกาจอย่างนึงของ Bluebird ที่ผมชอบคือมันสามารถแปลง module ที่เราใช้งานทั่วไปให้ใช้งานโดย return Promise object ได้ เช่น การใช้งานกับ module request เราสามารถทำให้ request ใช้งานแบบ Promise ได้โดย ใช้ method Bluebird.promisifyAll()

หลังจากนี้ถ้าต้องการให้ request return Promise เราสามารถใช้งาน method ชื่อเดิมทุกอย่าง แต่ให้เติม Async เข้าไปหลังชื่อ method นั้น เช่น ถ้าใช้งาน request.get()  หรือ request.post()  ให้ใช้งานแบบนี้ครับ

การจัดการกับ Promise Collection

ตัวอย่างโค้ดที่ผมเสนอไปเป็นการใช้งาน Promise เพียงอันเดียวแล้วถ้ามีหลายๆ อันล่ะเราจะจัดการมันยังไง? Bluebird เองเตรียม utilities function พวกนี้ไว้ให้แล้วครับ ยกตัวอย่างรูปแบบที่มีการใช้บ่อยๆ ดังนี้ครับ

join()

รับ parameter เป็น Promise object ได้หลายอัน คั่นด้วย comma

เราสามารถส่ง parameter เข้าไปได้มากกว่า 2 ตัวนะครับ ส่วน callback function ของ spread()  parameter ก็ตามจำนวน Promise ที่ส่งให้ join()  ตามลำดับ

all()

รับ parameter เป็น array ของ Promise object สมมุติว่าเราต้องการ request API หลายครั้งและอยากให้ทำพร้อมๆ กันเสร็จแล้วค่อยเอาผลมาใช้ต่อ ดูตัวอย่างโค้ดต่อไปนี้ครับ

props()

คล้ายกับ all()  แต่รับ parameter เป็น Hash Map ตอน get เอาผลลัพธ์ไปใช้งานสามารถระบุ key ได้เลย ตัวอย่างโค้ด

ยังมีการใช้งานแบบอื่นด้วยนะครับ แต่เคสที่ใช้งานน้อย ถ้าอยากรู้ว่ามีอะไรบ้างก็ดูได้จาก document ครับ (อ้างไปงั้นแหละ จริงๆ ขี้เกียจ)

then() และ spread()

Update (30 July 2017) Promise มาตรฐานใหม่ ไม่จำเป็นต้องใช้ spread() แล้ว สามารถใช้ then แทนได้เลย

จากตัวอย่างข้างบนจะเห็นว่าผมใช้ทั้ง then()  และ spread()  แล้วสอง method นี้มันต่างกันยังไง? ถ้าสังเกตดีๆ จะเห็นว่า then()  ผมใช้กับ Promise ก่อนหน้าอันเดียว ส่วน spread()  นั้นใช้กับ Promise หลายอัน เช่น

การใช้งานขั้นสูง (มั้ง)

จั่วหัวว่าการใช้งานขั้นสูงนี่ยังเขินเลยนะ จริงๆ เป็น tip เล็กๆ น้อยๆ สำหรับการใช้งาน Bluebird มากกว่าครับ สิ่งที่ผมอยากแนะนำคือการใช้งาน bind() เพราะมีประโยชน์มากกับการทำงานกับ Promise ที่มี then ต่อกันมากๆ

bind()  คือ method หนึ่งที่ทำให้เราสามารถทำ binding object เข้าไปทำงานใน Promise chain ได้ เมื่อเราใช้ keyword this ใน Promise chain จะสามารถ access method หรือ member ที่ binding ได้ สรุปง่ายๆ คือทำให้เราสามารถแชร์ทรัพยากรระหว่าง Promise chain ได้ สมมุติว่าเรามี Promise   3 chain ตามโค้ดนี้

ถ้าเราต้องการใช้งานผลจาก Promise object จาก block 1 มา block 2 เราก็แค่  return Promise จาก block 1 เราก็จะสามารถใช้งานผลใน block 2 ได้จาก parameter ของ then ใน block 2 ได้

แต่ … ถ้าต้องการส่งค่าที่ไม่ใช่ Promise จาก block 1 ไป block 2 ล่ะ? ถ้าต้องการใช้ผลจาก Promise ใน block 2 ใน block 4 ล่ะ จะทำยังไง?

คำตอบคือ binding object ว่างๆ เข้าไป แบบนี้

เวลาจะใช้งานก็แค่ใช้ this และ dot notation ตามด้วยชื่อ key เหมือนการใช้งาน Hash Map เช่น this.valueA จะ set ค่าให้เท่ากับอะไรก็ว่าไป เท่านี้เราก็สามารถ access ตัวแปรข้าม block ได้แล้ว แบบนี้ครับ

นอกจากนี้เรายังสามารถ bind keyword this  ได้ด้วยในกรณีที่ใช้ Promise ภายใน class และอยาก access member ของ class

ข้อควรระวังในการใช้ this

ถ้าเขียนโปรแกรมแนว OOP จะทราบว่า  this หมายถึงการอ้างถึงคลาสตัวมันเอง แต่สำหรับ JavaScript มันคือการอ้างถึง member ใน function หรือ object นั้นๆ จากตัวอย่างการใช้ this ข้างบนนี้ถ้าใช้ใน then เพียงชั้นเดียวก็โอเคใช้ได้ครับ แต่… ถ้าใช้ this ใน callback ลึกลงไปอีกชั้นมันจะไม่ใช่ this เดียวกันแล้วนะครับ ยกตัวอย่างโค้ดนี้

แม้ว่า guideline ของ JavaScript ส่วนใหญ่จะบอกให้หลีกเลี่ยงการใช้ this เพราะอาจเกิดข้อผิดพลาดแบบนี้ได้ แต่ถ้าจำเป็นต้องใช้จริงๆ สามารถทำแบบนี้ได้ครับ

สรุป

Promise เป็นแนวคิดการจัดการ control flow แบบใหม่ของ JavaScript ซึ่งมันช่วยลด callback ซ้อน callback  ทำให้โค้ดสวยงามขึ้นแต่อาจจะต้องทำความเข้าใจและปรับตัวกันนิดนึง อย่างไรก็ตามเราสามารถใช้งานรวมกับ control flow แบบ callback ได้นะครับไม่จำเป็นต้องเลือกอย่างใดอย่างหนึ่ง มองง่ายๆ มันก็เป็น object หนึ่งเท่านั้นครับ สามารถใช้ร่วมกันได้

ที่ผมยกตัวอย่าง Bluebird มานี้ไม่ใช่เพราะมีแค่ตัวนี้เจ้าเดียวนะครับยังมีอื่นๆ อีกเพียบ ยังมีตัวอื่นที่นิยมใช้กันรองลงมาก็ Q ครับแต่ที่ผมเลือกใช้ตัวนี้เพราะฟีเจอร์เยอะกว่า มี utility method ที่ช่วยให้ใช้งานสะดวกกว่า

สำหรับการ promisify module ที่เป็น callback ให้เป็น promise แม้ว่าจะไม่การันตีว่าทำงานได้ 100% กับทุก module หรือมีปัญหาเล็กน้อยกับบาง module  แต่ส่วนมากก็ใช้งานร่วมกันได้นะ  เพราะ module โดยทั่วไป design มาค่อนข้างดีอยู่แล้ว (ถ้าออกแบบเป็น CommonJS ก็น่าจะใช้งานกับ Bluebird ได้) จึงไม่น่ามีปัญหาอะไร module ที่เราเขียนเองก็ใช้งานได้เหมือนกันครับ

เว็บโครงการบน GitHub ของ Bluebird ครับ เข้าไปอ่าน documents ดูฟีเจอร์อื่นกันได้เลย https://github.com/petkaantonov/bluebird

 

Be First to Comment

Leave a Reply

Your email address will not be published.