ปัญหาอย่างหนึ่งของ Node.js (และ JavaScript) ซึ่งใช้ ES5 ที่มักจะถูกโจมตีจากสาวกภาษาอื่นเสมอมา (หรือแม้แต่คนเขียน Node.js อยู่แล้วก็เถอะ) คือเรื่อง callback hell ซึ่งจริงๆ มันก็ทำงานได้แหละครับแต่หน้าตาโปรแกรมจะแย่มากๆ แบบนี้
funcA(function () { funcB(function () { funcC(function () { //do something }); }); });
ถึงแม้ว่า Node.js เวอร์ชันล่าสุด (Version 4) หรือ io.js จะรองรับ syntax ใหม่ๆ ตามมาตรฐาน ES6 ซึ่งทำให้โค้ดสวยขึ้น แต่โค้ดส่วนมากก็ยังเขียนด้วย Es5 อยู่และธรรมชาติของ JavaScript เองที่จัดการการทำงาน Asynchronous ด้วย callback ดังนั้นเราหนีไม่พ้นหรอกครับ ที่ทำได้คือลด callback ซ้อน callback ให้มากที่สุดจนดูไม่เละเทะจนเกินไปกันดีกว่า ที่ผมแนะนำในโพสต์นี้คือ lib 2 ตัว Async และ Bluebird ครับ
Async
เป็น lib จัดการ control flow สไตล์ callback มี function สำหรับการทำงานหลายแบบทั้ง waterfall(), series(), parallel(), map(), mapSeries(), times() etc.
ยกตัวอย่างการใช้งาน async สักหน่อยแล้วกัน สมมุติว่าผมมีงานที่ต้องทำตาม step 1 2 3 แต่ละ step ได้ผลลัพธ์ออกมา โดยผลนั้นต้องถูกส่งต่อกันเรื่อยๆ ถ้าการทำงานเป็นแบบนี้ function ที่ผมต้องใช้คื อ waterfall() หรือการทำงานแบบขั้นน้ำตกนั่นเอง เขียนโค้ดจะได้แบบนี้ครับ
async.waterfall([ function (cb) { cb(null, result1_1, result1_2); }, function (resultFrom1_1, resultFrom1_2, cb) { var result2 = process(resultFrom1_1, resultFrom1_2); cb(null, result2); }, function (final, cb) { cb(null, process(final)); } ], function (err, finalResult) { console.log(finalResult); });
หรือถ้าต้องการทำหลาย function แบบ parallel ก็ทำแบบนี้ครับ
async.parallel([ func1(cb), func2(cb), func3(cb) ], function (err, results) { console.log(results); });
ถ้าอยากทำเป็น step เรียงกันแบบไม่มีการส่งต่อผลลัพธ์ก็ใช้ series() รูปแบบการใช้งานเหมือน parallel() แต่จะทำงานเรียงกันแทน ผลลัพธ์ของ callback สุดท้ายคือ error และ array ผลลัพธ์ของ function ที่เราส่งเข้าไปประมวลผล
รูปแบบการใช้งานอื่นๆ สามารถดูได้จาก document เลย Async มี document ค่อนข้างดี มีตัวอย่างให้ดูเยอะ
Ref: https://github.com/caolan/async
Bluebird
สำหรับมือใหม่อาจจะแปลกๆ นิดนึงกับการใช้งาน lib นี้เพราะใช้ Promise pattern ในการจัดการ control flow แนวคิดการทำงานของ promise ทำงานต่อกันในลักษณะ chain เช่น ทำ A แล้ว B เสร็จแล้วทำอะไรต่อ ผลลัพธ์จาก A -> B -> C ก็จะส่งต่อกันไปเรื่อยๆ (when … then …)
Promise เป็น object ที่เก็บสถานะการทำงานของ function ไว้ Promise object มีได้หลายสถานะได้แก่
- Fulfilled ทำงานสำเร็จ
- Rejected ทำงานไม่สำเร็จ
- Pending รอการประมวลผล
- Settled ทำงานเสร็จแล้วแต่อาจจะ reject หรือ fulfilled ก็ได้
ล่าสุด Promise ถูกรวมเข้ามาใน ES6 แล้วสามารถใช้งานได้เลย แต่เพื่อความสะดวกแนะนำว่าใช้ Bluebird ดีกว่าครับเพราะมี function ช่วยทำงานหลากหลายกว่า สะดวกกว่าด้วย
โค้ดที่เขียนด้วย Promise pattern โดยใช้ bluebird หน้าตาจะเป็นแบบนี้ครับ
var mytask = MyTask.doSomething(); // return promise object mytask.then(function (val1) { return val2Promise(); }).then(function (val2) { return val3Promise(); }).then(function (val3) { // do something }).catch(function (e) { console.log(e.stack); throw e; }).finally(function () { done(); });
จะเห็นว่าโค้ดสวยงามขึ้นเหมือนเพราะ callback ซ้อน callback ลดลง ถ้าเป็นภาษาโปรแกรมทั่วไปก็คล้าย try … catch มากเลยครับ (แต่ไม่เหมือนกันนะ) การใช้งานแบบอื่นๆ ขอเอาไปเขียนโพสต์หน้าแล้วกันครับเพราะฟีเจอร์เยอะมาก
Ref: https://github.com/petkaantonov/bluebird
เลือกใช้กันตามสะดวกครับ