NodeJS ตอนที่ 7 [การเขียนโปรแกรมแบบ Callback]

Java Script Callback ก็คือ การเรียกฟังก์ชั่นแบบ Asynchronous
เอ่ะ แล้วการเรียกแบบ Asynchronous มันคืออะไรอีกเนี่ย ?
คำว่าAsynchronous กับ Synchronous ในแวดวงวิศวกรรมหรือเทคโนโลยีมักจะได้ยินกันบ่อยๆ ซึ่งมักใช้เกี่ยวกับการรับส่ง ข้อมูล สัญญาณ ในรูปแบบต่างๆ ซึ่งการส่งข้อมูลหรือสัญญาณต่างๆนั้นก็
ต้องมี ผู้ร้องขอ – ผู้ตอบกลับ
การส่งแบบ Synchronous

นั้นเป็นการรอจังหวะให้การร้องขอและการตอบกลับเสร็จเป็นเรื่องๆไปจึงจะทำงานตามขั้นตอนการร้องขอครั้งใหม่

[img src=”/wp-content/uploads/2018/08/sync.png” class=”aligncenter”]

การร้องขอครั้งที่ 2 จะเกิดขึ้นได้ก็ต่อเมื่อ การร้องขอครั้งแรกได้รับการตอบกลับแล้ว ซึ่งหากผู้ตอบสนองทำงานได้ช้า ก็จะเกิดปัญหาคอขวด (Bottleneck)เกิดขึ้น

ซึ่งในเหตุการณ์ชีวิตจริง ก็เปรียบได้กับการไปสั่งกาแฟในร้านที่ไม่มีบัตรคิว และไม่จดเมนูให้ หากผู้ร้องขออยากได้กาแฟก็ต้องรอจนถึงรอบตัวเองเท่านั้นถึงจะได้สั่งและรอต่อไป

เปรียบเทียบกับการเขียนโปรแกรม
คำสั่งที่ 1 getUserInfo

คำสั่งที่ 2 getProductList

* คำสั่ง getProductList จะไม่เกิดขึ้นเลยถ้า getUserInfo ยังไม่เสร็จ
ตัวอย่าง JavaScripthttp://jsfiddle.net/apaichon/z59csxLk/
function getUserInfo(){
 for(var i=0;i<10;i++){
   console.log('User Info no ' + i);
  }

}
function getProductList(){
 for(var i=0;i<10;i++){
   console.log('Product no ' + i);
  }
}

getUserInfo();
getProductList();
การส่งแบบ Asynchronous

การส่งแบบ Asynchronous ก็จะตรงกันข้ามกับ Synchronous คือ สามารถส่งคำร้องขอไปได้เรื่อยๆ ไม่ต้องรอให้คำขอก่อนหน้าเสร็จก่อน ซึ่งในชีวิตจริง ก็เหมือนการเดินเข้าไปสั่งอาหารในร้าน Junk Food หรือ ร้านกาแฟ ที่มีการบริหารจัดการเรื่องคิว รับออเดอร์ลูกค้ามาก่อน ยังไม่จำเป็นต้องทำเสร็จ ก็รับออเดอร์ถ้ดไปได้เลย ก็จะทำให้ไม่เสียโอกาสในการให้บริการลูกค้าท่านอื่นๆ ซึ่งการเขียนโปรแกรมแบบ Asynchronous ในปัจจุบันก็มาพร้อมการบัญญัติศัพท์ใหม่ๆให้ดูเท่ห์ คือคำว่า None Blocking I/O

ในการเขียนโปรแกรมติดต่อฝั่งเซิร์ฟเวอร์ในงานจริง หากฟังก์ชั่น getUserInfo และ getProductList เป็นการร้องขอไปยังเครื่องเซิร์ฟเวอร์คนละเครื่อง หรือเซิร์ฟเวอร์เครื่องเดียวกัน แต่ผู้ใช้งานอยู่คนละประเทศ ฟังก์ชั่น getProductList ก็อาจทำงานเสร็จก่อน getUserInfo ได้ ซึ่งผู้พัฒนาต้องเข้าใจลำดับการทำงานของโปรแกรมแบบ Asynchronous เพื่อนำผลลัพธ์ที่ได้ไปใช้อย่างถูกต้อง

ตัวอย่าง JavaScript http://jsfiddle.net/apaichon/27aqLfnc/
function getUserInfo(callback){
    setTimeout(function(){
     callback('got user info!');
    },500);
}
function getProductList(callback){
    setTimeout(function(){
      callback('got product list!');
    },100);
}

   getUserInfo(function(result){
    console.log(result);
   });
   getProductList(function(result){
    console.log(result);
   });

จากตัวอย่าง เรียงลำดับการเรียงฟังก์ชั่น getUserInfo ตามด้วย getProductList แต่เอ่ะทำไม getProductList เสร็จก่อนละ ในตัวอย่างนั้นเงื่อนไขชัดเจนคือกำหนดเวลา Timeout ไว้ แต่ในชีวิตจริงการเขียนโปรแกรมแบบ Asynchronous ติดต่อ Server และฐานข้อมูล เป็นเรื่องที่คาดเดาได้ยากว่าอันไหนจะเสร็จก่อน หากวางโค้ดตามลำดับแบบนี้ ถ้าในโปรแกรมต้องให้ UserInfo เสร็จก่อนถึงทำ getProductList รับรองว่าลำดับการทำงานของโปรแกรมผิดแน่ๆ

รูปแบบการประกาศฟังก์ชั่นแบบ Callback
function name(option,callback){
 callback(result);
}
  • callback – ใช้คืนค่าผลลัพธ์จากการ callback  สามารถเขียนเป็นอะไรก็ได้ตามที่ระบุไว้ในตอนรับค่ามาเช่น  function a(opt,f){f(result);}
  • callback() – ส่งค่าผลลัพธ์กลับไปยังฟังก์ชั่นที่เรียก
  • result – ผลลัพ์ธ์ที่ส่งกลับไป สามารถส่งเป็นรูปแบบ json ฟังก์ชั่น variable ต่างๆที่ javascript รองรับ

การเรียกใช้

  • name(function(result){
    });
  •  result – ผลลัพธ์ที่ได้รับจากการ callback จะตั้งชื่ออะไรก็ได้ที่ตรงหลักการของ JavaScript ไม่ตรงกับคำสงวน ไม่ตรงกับชื่อตัวแปรที่เป็น global variable

แล้ว callback มันไม่ดียังงัยละ 

สรรพสิ่งบนโลกนี้ล้วนอนิจจัง ไม่มีอะไรสมบูรณ์แบบ เมื่อเราแก้ปัญหาอีกอย่าง เราก็จะเจอปัญหาอีกอย่าง สำหรับ callback สิ่งที่นักพัฒนาเจอปัญหาเท่าที่ทราบน่าจะมี ดังนี้

  • หากลืมเขียน callback() เพื่อส่งผลลัพธ์กลับ ก็จะไม่ทราบว่าโปรแกรมทำงานถึงไหน เงียบหายไปเลย อันนี้ส่วนใหญ่เป็นข้อผิดพลาดของนักพัฒนาเอง
  • หากต้องการผลลัพธ์ตามลำดับขั้น การเขียนฟังก์ชั่นที่ซับซ้อน callback ช่วยให้คุณปวดหัวได้มากขึ้นแน่นอน ที่เราเรียกว่า Callback Hell

 ตัวอย่าง

login(function(userInfo){
 getMenu(userInfo,function(menuList){
  genMenu(menuList,function(status){
   showDefaultPage(status,function(){

   });
   log(userInfo,status);
  })

ขั้นตอนการทำงาน

  • เมื่อ login เสร็จได้ข้อมูล userInfo
    • ทำการดึงข้อมูล menu ที่ผู้ใช้งานมีสิทธิเข้าถึงได้
      • genMenu ทำการสร้างเมนูบนหน้าจอ
        • ทำการแสดงผลหน้าที่กำหนดเริ่มต้น
        • ทำการ log ข้อมูลการเข้าระบบของผู้ใช้

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

ตัวอย่าง callback hell

  1. สร้างไฟล์ชื่อ ch7_callback.js
  2. ป้อนโค้ด ดังนี้
function a(n,cb){
  cb(n);
}
a(1,function(n1){
    a(2,function(n2){
      a(3,function(n3){
        a(4,function(n4){
          a(5,function(n5){
            a(6,function(n6){
              a(7,function(n7){
                n1+=n2+n3+n4+n5+n6+n7;
                console.log(n1);
              })
            })
          });
        });
      });
    })
});

3. รัน node ch7_callback.js

node ch7_callback.js
28

จากตัวอย่างจะเห็นได้ว่าถ้ามีการเขียนโค้ดแบบ asynchronous แล้วต้องรอผลลัพธ์จากอีกฟังก์ชั่นหนึ่ง แล้วมีลำดับต้องทำงานต่อไปเรื่อยๆ โค้ดจะดูน่าเกลียดมาก ซึ่งในตัวอย่างนี้ยังไม่มีการเขียนติดต่อกับไฟล์หรือฐานข้อมูลทำให้อาจจะยังให้มือใหม่ยังไม่เข้าใจเกี่ยวกับ callback และ Asynchronous ได้ 100 % ไว้ในตอนต่อๆไปจะมีการเขียน callback และนำ ไลบรารี่ต่างๆที่เข้ามาช่วยให้บริการจัดการโค้ดได้ดีขึ้น ฝึกบ่อยๆแล้วจะเข้าใจขึ้นเรื่อยๆ

ติดตามกับตอนที่ 8 การเขียน Asynchronous ด้วย Promise