4 วิธีเล่นกับ Asynchronous Function ใน JavaScript

tonkla
tonkla
Jul 30, 2017 · 4 min read

TL;DR

  • Callback
  • Promise
  • async/await
  • Rx (Observable)

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

In mathematics, a function is a relation between sets that associates to every element of a first set exactly one element of the second set. Typical examples are functions from integers to integers or from the real numbers to real numbers.

แปลเป็นภาษาบ้านๆ ตามความเข้าใจส่วนตัว มันคือความสัมพันธ์ระหว่าง input กับ output เช่น f(x) = x² หมายถึงฟังก์ชันที่ให้ output เป็นค่ายกกำลังสองของ input

ส่วนฟังก์ชันในทางโปรแกรมมิ่งหมายถึงชุดของโค้ดที่ทำงานอย่างใดอย่างหนึ่งโดยเฉพาะ มีการรับอินพุทที่เรียกว่า “พารามีเตอร์ (parameter)” (ส่วน “อาร์กิวเมนท์ (argument)” ใช้สำหรับเรียกอินพุทฝั่งขาส่ง) โปรแกรมที่ดีจะต้องแตกโค้ดออกมาเป็นฟังก์ชันย่อยๆ ซึ่งมีข้อดีมากมาย เช่น readability, maintainability, unit testability, reusability

function add(x: number, y: number): number {
return x + y;
}
add(1, 2);

ในโลกของ JavaScript ฟังก์ชันถือเป็นพลเมืองชั้นหนึ่ง (first-class citizen) เทียบเท่ากับ primitive type ทั้งหลาย เช่น int, float, string, boolean นั่นคือเราสามารถ

  1. ส่งฟังก์ชันเข้าไปเป็นอาร์กิวเมนท์ของฟังก์ชันอื่นได้ (higher-order function)
  2. รีเทิร์นค่าออกมาเป็นฟังก์ชันได้
  3. กำหนดค่าให้ตัวแปรเป็นฟังก์ชันได้ หรือจับยัดใส่ data structure ใดๆ ก็ได้
  4. รองรับการเขียน anonymous function ได้

หากภาษาใดมีคุณสมบัติทั้ง 4 ข้อดังกล่าว ภาษานั้นจัดว่าเป็น “first-class function”

JavaScript เป็นภาษาที่ไม่มี official runtime เป็นเพียงสเปค ที่ออกโดยองค์กร ECMA International ซึ่งเดิมทีมีการใช้งาน JavaScript เฉพาะบนเว็บไซต์เพียงอย่างเดียว บริษัทที่ทำเว็บเบราเซอร์ต่างก็ทำ JavaScript engine ของตัวเองออกมา เช่น SpiderMonkey ของ Mozilla ที่ใช้ใน Firefox หรือ V8 ของ Google ที่ใช้ใน Chrome

JavaScript (ECMAScript, ES) ถูกออกแบบมาให้ทำงานเป็น single thread นั่นหมายความว่าการรัน synchronous function จะต้องมีการหยุดรอผลลัพธ์เสมอ (blocking function) งานที่ทำเสร็จเร็ว ก็จะรอแค่ช่วงสั้นๆ ส่วนงานที่เสร็จช้า จำพวก I/O ต่างๆ เช่นการเรียก HTTP, การอ่านไฟล์ ก็ต้องรอจนกว่ามันจะเสร็จ งานอื่นๆ ที่เหลือก็จะถูกบล็อคไว้ อาการแบบโปรแกรมค้าง

ส่วน asynchronous function นั้น เรียกอีกอย่างว่า non-blocking function หลังเรียกใช้แล้วก็แค่ฝากเบอร์โทรกลับ จากนั้นจะสามารถไปทำงานอื่นที่ไม่เกี่ยวข้องกับ output ของ function นี้ต่อได้เลยทันที ซึ่ง “เบอร์โทรกลับ” ที่ว่านี้แบ่งออกได้ 4 วิธี คือ

1. Callback

document.getElementById('x').addEventListener('click', function() {
console.log('DOM #x has been clicked.')
})

หรือในรูปแบบกระทัดรัดอ่านง่ายของ jQuery

$('#x').click(function() {
console.log('DOM #x has been clicked.')
})

เมื่อโค้ดเรามีขนาดใหญ่ขึ้น มีการทำงานที่ซับซ้อนขึ้น เราจะเจอการเรียกใช้ callback ซ้อน callback ซ้อน callback ซึ่งมีชื่อเรียกกันในวงการว่า “Callback Hell” วิธีการแก้ไขเบื้องต้นคือยกฟังก์ชันออกมาเขียนแยกต่างหาก ไม่เขียนแบบ anonymous function ซึ่งในลิงก์ได้อธิบายวิธีแก้ปัญหาไว้แล้ว

2. Promise

function isValid(param) {
return new Promise((resolve, reject) => {
try {
// do some work
if (valid) resolve(true)
else resolve(false)
} catch (err) {
reject(err)
}
})
}

resolve ใช้สำหรับส่งกลับ (yield) ค่าปกติที่เกิดจากการทำงานของฟังก์ชัน
reject ใช้สำหรับส่งกลับค่าที่ผิดปกติ เช่น error, exception

หน้าตาฝั่งขาเรียกใช้งาน

isValid(arg).then(param => {
if (valid) {
} else {
}
}).catch(e => {
// handle the error
})

.then() จะรับค่าที่ถูกส่งมาจากฟังก์ชัน resolve()
.catch() จะรับค่าจากฟังก์ชัน reject()

3. async/await

const validateSomething = async input => {
if (await isSomething(input)) {
return output
}
}
const output = await validateSomething(arg)

จำง่ายๆ ว่า ใช้ await แทน .then และฟังก์ชันไหนที่มีการเรียกใช้ await ภายใน จะต้องแปะคำว่า async ที่หัวฟังก์ชันด้วย

4. Reactive Programming (Observable)

คำจำกัดความของ Reactive Programming หรือ Rx หรือ Observable คือ

Observable is just a function that takes an observer and returns a function

มีบล็อกมากมาย (หาได้จาก Google เช่น นี่ , นี่ , นี่ , นี่ , นี่) ที่พยายามอธิบายว่า Observable คืออะไร สำหรับผม (ซึ่งเคยงงกับมันมาหลายวัน) สุดท้ายเข้าใจว่ามันก็คือ Promise ที่สามารถยิงค่าออกมาได้หลายๆ ค่า โดยมี Operator ซึ่งก็คือฟังก์ชันทั้งหลายที่สามารถใช้ได้กับ Enumerable Object (Array, Hash) เช่น map, reduce, filter ซึ่งสามารถ chain ต่อๆ กันได้ โดย output ของฟังก์ชันก่อนหน้าจะเป็น input ของอีกฟังก์ชันตัวถัดไป สำหรับ Observable เราสามารถใช้กับ single source เช่น Array หรือจะใช้กับ multiple sources เช่น Stream (ยกตัวอย่าง event การเคลื่อนที่ของ mouse) ก็ได้

Credit: http://www.fancydogwebdesign.com/news/an-animated-intro-to-rxjs/

Ben Lesh ซึ่งเป็นหัวหน้าทีม RxJS 5+ ได้เขียนคำอธิบายที่เข้าใจได้ง่ายๆ ไว้ดังนี้

Promises are always multicast. Promise resolution and rejection is always async. Observables are sometimes multicast. Observables are usually async.

Observable ฝั่งขาส่งจะมี 3 ฟังก์ชัน คือ next(), complete(), และ error() หน้าตาประมาณนี้

const hello = new Observable(observer => {
try {
// do some work
observer.next('Hello')
observer.complete()
} catch (e) {
observer.error(e)
}
})

ส่วนฝั่งขารับจะใช้ subscribe() ร่วมกับ operator ต่างๆ เช่น map(), filter()

hello.map(val => `${val}, World`)
.subscribe(val => console.log(val), err => console.error(err))
// Success output: Hello, World

มีคนสงสัยว่าเมื่อไหร่ควรจะใช้ Promise เมื่อไหร่ควรจะใช้ Observable บางคำตอบบอกว่าสำหรับ single source / single value เช่น Array ก็ให้ใช้ Promise ไป ส่วน multiple sources / multiple values เช่น Stream ต่างๆ ก็ให้ Observable แต่บางคำตอบก็บอกว่าควรใช้ Observable สำหรับทุกๆ อย่างเลย เพราะว่า…

tonkla

Written by

tonkla

He enjoys writing clean and elegant codes, then seeing them work properly, reliably and fast.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade