Skip to content

การจัดการ Control flow สำหรับ Node.js

ปัญหาอย่างหนึ่งของ 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

เลือกใช้กันตามสะดวกครับ

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.