ควบคุมการทำงานใน [Node.js ,Js] ให้อยู่หมัด ด้วย [callback , promise , async await]

bakatest to
bakatest.me
Published in
5 min readJun 24, 2017

เมื่อผมเริ่มเขียน javascript , Node.js ครั้งแรก มักเจอปัญหาใหญ่ๆด้วยความที่ไม่รู้…เห้ยทำไมค่าจาก code ที่เขียน บรรทัดนี้ทำไมมันทำก่อน ทั้งๆที่บรรทัดก่อนหน้านี้มันยังทำงานไม่เสร็จเลย ตัวอย่างเช่น

/* run onnode.js version 7.10 */const axios = require('axios');/*use package "npm i axios -S"*/var instance = axios.create({baseURL: 'https://unsplash.it/'});function getData() {    /*get data from api https://unsplash.it*/    instance.get('/list').then(function (response) {      console.log('main todo');      console.log("response total %s <",response.data.length);    }).catch(function (error) {      console.log("if error");    });}function main() {    console.log('main work');    getData();    console.log("main end");}main();

ผลลัพธ์ที่ได้

main work <-ทำงานอันแรกmain end <-ข้ามมาอันสุดท้ายmain todo <- ทำงานพึ่งเสร็จresponse total 1013 < <- ทำงานพึ่งเสร็จ

ซึ่งจริงๆแล้วที่เราอยากได้ คือให้ main todo ทำงานเสร็จก่อน แถมต้องการข้อมูลที่ได้จาก function getData(); เพื่อนำไปใช้อีกใน function อื่นๆ อีก

ส่งที่ต้องเตรียม

  • Node.js 7.10 เพราะ async/await support version นี้ขึ้นไป เช็คได้จาก node.green สำหรับ version ที่น้อยกว่านั้น หรือ front-end(javascript) สามารถใช้ได้เช่นกัน โดยติดตั้ง Transpiler babel stage-0 เพื่อแปลง code ที่เราเขียนให้เป็น es5
  • *ในแต่ละตัวอย่างผมใช้ package ‘axios’ เพื่อดึง api จากเว็บ https://unsplash.it/
  • เตรียมใจให้พร้อมเราทำได้…. 😄

ปัญหาเหล้านี้จะหมดไป…ผมขอเสนอ 3 วิธี

Hello Callback (Hell) ไม่แนะนำนาจาแต่รู้ไว้ก็ไม่เสียหาย

/* run onnode.js version 7.10 */const axios = require('axios');/*use package "npm i axios -S"*/var instance = axios.create({baseURL: 'https://unsplash.it/'});function getData(cb) {     /*step2 รับ function เข้ามาผ่าน parameter ด้วยตัวแปร cb*/     instance.get('/list').then(function (response) {         /*step3 โยนค่ากับไป โดยเรากำหนด param ตัวแรก        เป็นที่สำหรับกรณี error  ตัวที่สอง สำหรับส่ง ข้อมูลกลับ*/       cb(null,response.data);    }).catch(function (error) {       cb("if error");/*step3 กรณี error*/    });}function main() {    console.log('main work');    /*step1 เราโยน function ลงไปเป็น parameter*/    getData(function(error,result){    /*step4 รอรับค่าจากที่ step3 ส่งกลับมา โดยเราอาจดัก error เเบบนี้    if(error){         ทำบางอย่างเมื่อ error    }*/    console.log('main todo');    console.log("response total %s <",result.length);    console.log("main end") });}main();

function getData(); เรารับ parameter cb ซึ่งสิ่งที่เรารับมานั้นคือ function

function getData(cb) {     instance.get('/list').then(function (response) {        cb(null,response.data);     }).catch(function (error) {        cb("if error");/*step3 กรณี error*/     });}

เมื่อเราต้องการโยนค่ากลับไปยัง getData(); ที่ถูกเรียกใช้ เราก็จะใช้ cb โดยสั่งให้ cb ทำงาน โดย cb() คือเรียกใช้ function นั่นเอง โดยผมได้แนบ parameter กลับไปด้วย กำหนดให้ parameter

  • ตัวแรกนั้นเป็น กรณี่ที่ error เท่านั้น และ parameter
  • ตัวที่สองเป็น กรณี ต้องการส่งข้อมูลกลับไป
cb("case error","send result");

ตอนเราเรียกใช้ function getData(); เราโยน function ลงไปเป็น parameter ของ function getData() เพื่อจะรอรับ callback function ที่เราเรียกผ่าน cb() ตามตัวอย่างนี้

getData(function(error,result){   //รอรับค่าที่กลับมาจาก callback})

หลายคนอาจมองว่าแล้วมันยังไงหละ ก็เเค่ส่งค่ากลับมานิ

คำตอบ : ใช่ครับ

แต่มันมีเห็นผลอยู่ว่าทำไมถึงมี คำว่า HELL ต่อท้าย

ตัวอย่างเมื่อ เราต้องทำงานกับ function getData(); หรือที่คล้ายๆกรณีนี้ ใน code ใน function main() ก็จะเปลี่ยนไปเป็นอย่างนี้

function main() { getData(function (error, result) {     getData(function (error, result) {          getData(function (error, result) {              getData(function (error, result) {             // HELL หรือยังครับ เเต่ใครว่ายัง หรือชอบ ก็ตามสบายครับ ไม่ขัดศรัทธา              });          });      }); });}

HELL หรือยังครับ เเต่ใครว่ายัง หรือชอบ ก็ตามสบายครับ ไม่ขัดศรัทธา

Hello Promise(เราจะทำตามคำสัญญาขอเวลาอีกไม่นานน…)

/* run onnode.js version 7.10 */const axios = require('axios'); //use package "npm i axios -S"var instance = axios.create({baseURL: 'https://unsplash.it/'});function getData() {     /*step2 ใช้คำสั่ง new Promise ,     return new Promise เพื่อต้องการส่ง promise กลับ*/    return new Promise(function (resolve, reject) {        instance.get('/list').then(function (response) {          /*step3 โยนค่ากับไป ผ่าน reslove */          resolve(response.data);        }).catch(function (error) {          /*step3 กรณี error โยนค่ากับไป reject*/          reject("if error");        });    })}function main() {     console.log('main work');     /*step1 เราโยน function ลงไปเป็น parametor*/     getData().then(function(result){        console.log('main todo');        console.log("response total %s <", result.length);        console.log("main end")     }).catch(function(error){        console.log("case error");     })}main();

hell Promise โดยใช้คำสั่ง new Promise

new Promise(function (resolve, reject) {    resolve("result");    reject("if error");});

โดยเมื่อ code ทำงานมาถึง resolve หรือ reject จะทำการส่งค่ากลับทันที (เอาจริงมันก็ไม่ได้ ดีดตัวออกเหมือน break; หรือ return ผมชอบเขียนแบบนี้เพื่อให้มันหยุดเลย)

new Promise(function (resolve, reject) {    return resolve("result");    return reject("if error");});

ใส่ return ไปเลย ฮาๆ

เมื่อนำมาใช้งานกับ function getData(); ก็จะเป็นแบบนี้

function getData() {      /*step2 ใช้คำสั่ง new Promise ,      return new Promise เพื่อต้องการส่ง promise กลับ*/      return new Promise(function (resolve, reject) {           instance.get('/list').then(function (response) {           /*step3 โยนค่ากับไป ผ่าน reslove */           resolve(response.data);      }).catch(function (error) {           /*step3 กรณี error โยนค่ากับไป reject*/           reject("if error");      });    })}

และตอนเรียกใช้ function getData(); โดยใช้

  • nameFunction().then ต่อท้าย function ที่ถูกเรียกใช้งาน และภายใน .then จะมี function .then(function(…param ที่ส่งกลับผ่าน resolve
  • .then(…).catch สำหรับเอาไว้ดักจับ error ภายใน Promise หรือ กรณี reject ตัวอย่างก็ตามนี้
getData().then(function(result){         console.log('main todo');         console.log("response total %s <", result.length);         console.log("main end")}).catch(function(error){         console.log("case error");})

ตัวอย่างเมื่อ เราต้องทำงานกับ function getData(); หรือที่คล้ายๆกรณีนี้ ใน code ใน function main() ก็จะเปลี่ยนไปเป็นอย่างนี้

getData().then(function (result) {   getData().then(function (result) {     getData().then(function (result) {       getData().then(function (result) {         console.log('result',result.length);         // ผมว่าก็ HELL ดีนะครับ สวยดี       })     })   })})

แต่เดี๋ยวก่อนเราสามารถเขียนแบบนี้เพื่อให้ code สวยขึ้นครับ

getData().then(function (result) {    return getData();}).then(function(result){    return getData();}).then(function(result){    return getData();}).then(function(result){    return getData();}).then(function(result){   console.log('BAKA result',result.length);})

I love async / await (^ ^)

/* run onnode.js version 7.10 */const axios = require('axios'); //use package "npm i axios -S"var instance = axios.create({baseURL: 'https://unsplash.it/'});/*เมื่อต้องการใช้ await ประกาศ async หน้า function เสมอ */async function getData() {     /*await หน้า function promise ที่ต้องการให้รอ*/     return await instance.get('/list');}/*เมื่อต้องการใช้ await ประกาศ async หน้า function เสมอ */async function main() {   try {        /*await หน้า function promise ที่ต้องการให้รอ*/        let result = await getData();        console.log('BAKA result',result.data.length);   } catch (error) {        /*ดักจับ error */        console.log('case error',error.code);   }}main();

บรรทัด code ลดไปเยอะเลยครับ โดยส่วนตัวผมชอบมาก code ดูอ่านง่ายขึ้นมาดูวิธีใช้กัน

การทำงานของ async await ไม่ใช่สิ่งแปลกใหม่เลย เรามาดูกัน เริ่มจากใส่ async นำหน้า function ที่เราต้องการจะใช้ await โดย await อยู่ภายใต้ function ใหนให้ async นำหน้า function นั้นๆ

async function getData() {    //await something}/*or*/function baka(async function(){    //await something})

await นำหน้า function ที่เป็น promise

async function getData() {      return await instance.get('/list');}

เอ๊ะ แล้วมันไม่ใช่สิ่งแปลกใหม่ยังไง ??

  • จะใช้ await ได้ต่อเมื่อมี function แล้วถ้า เรียกใช้ function getData() นอก function ละทำไง
getData().then(function(result){    //receive result})

เอ๊ะ คุ้นๆๆใหม Promise นั่นเองครับบบบ

ตอนเรียกใช้ใน function main(); ตามตัวอย่าง

async function main() {       try {              let result = await getData();              console.log('BAKA result',result.data.length);       } catch (error) {              console.log('case error',error.code);       }}

โดย async / await สามารถทำงานร่วมกับ try / catch ได้ เพื่อดักจับ error ครับเป็นไงครับง่ายใช่ใหมครับ แต่อีกข้อหนึ่งที่ควรรู้ไว้ async / await ไม่สามารถทำงาน กับ callback function ได้ ตัวอย่างเช่น

function getData(cb){     cb("my name is callback hell")}async function main() {     try {        let result = await getData();        console.log('BAKA result',result.data.length);     } catch (error) {        console.log('case error',error.code);     }}
main();

กรณีนี้เข้า catch เเน่นอน แต่เราก็มีวิธีแก้ โดยเรานำ Promise มาช่วยนั่นเอง

function getDataWithCallback(cb){     cb("my name is callback hell")}function getDataWithPromise() {     return new Promise(function (resolve, reject) {         getDataWithCallback(function(error,result){           if(error) return reject(error);           return resolve(result);         })     });}async function main() {    try {        let result = await getDataWithPromise();        console.log('BAKA result',result.data.length);    } catch (error) {       console.log('case error',error.code);    }}
main();

เอ๊ะแล้วงี้ มันก่อคล้ายๆ promise นิมันต่างกันไงอะ ????

ตัวอย่างเมื่อ เราต้องทำงานกับ function getData(); หรือที่คล้ายๆกรณีนี้ ใน code ใน function main() ก็จะเปลี่ยนไปเป็นอย่างนี้

async function main() {    try {           let result1 = await getData();
let result2 = await getData();
let result3 = await getData();
let result4 = await getData();
let result5 = await getData();
let result6 = await getData();
console.log('BAKA result',result6.data.length); } catch (error) { console.log('case error',error.code); }}

code จะดูสวยขึ้นมาทันทีเลย โดยส่วนตัวผมชอบเเบบนี้มาก รักเลย

ใครจะใช้วิธีใหนก็แล้วแต่ ทุกวิธีที่เขียนมา สุดท้าย ก็ได้ผลลัพธ์ของโปรแกรมเหมือนกันหมด

code ตัวอย่าง https://github.com/bakatest-me/nodejs-callback-promise-async-await

แปะ อ้างอิง https://www.twilio.com/blog/2015/10/asyncawait-the-hero-javascript-deserved.html

--

--

bakatest to
bakatest.me

“ba — ka” is japanese word, mean stupid or crazy. “test” is english word, just test. Why baka + test ? because i like to test manythigs with stupid way. Lol