เมื่อเราเรียกโปรแกรมมาทำงาน (run) จะเกิดสิ่งที่เรียกว่าโปรเซส (process) ซึ่งโปรเซสคือหน่วยของการดำเนินการ (execution) โดยโปรเซสจะมีการจองพื้นที่หน่วยความจำเป็นของตนเอง ดังนั้นถ้าเราเรียกใช้โปรแกรมมากกว่าหนึ่งโปรแกรมจะเกิดโปรเซสและการจองพื้นที่หน่วยความจำของแต่ละโปรแกรมและการใช้งานจะไม่ปะปนกัน ภายในโปรเซสจะมีหน่วยการดำเนินการที่แยกย่อยลงไปเรียกว่าเธรด (thread) เธรดที่เกิดจากโปรเซสเดียวกันจะใช้ไฟล์และพื้นที่หน่วยความจำร่วมกัน
ลำดับการทำงานของส่วนต่างๆในโปรแกรมจะถูกเก็บลงในโครงสร้างข้อมูลที่เรียกว่าแสต็ก (stack) โดยการทำงานของแสต็กคืออะไรที่ใส่ลงไปในแสต็กล่าสุดจะถูกหยิบขึ้นดำเนินการก่อน เราเรียกการทำงานในลักษณะนี้ว่า LIFO (Last In First Out) ตัวอย่างเช่น ในภาษาจาวาเมื่อเริ่มดำเนินการตามคำสั่งในเมธอด main() เมธอด main() จะถูกใส่ลงไปในแสต็กก่อน จากนั้นเมื่อเมธอด method1() ถูกเรียกใช้งานจึงถูกใส่ตามลงไป และตามด้วยเมธอด method2() และเมื่อทำงานของเมธอด method2() เสร็จแล้วก็จะถอนเมธอด method2() ออกจากแสต็กแล้วไปทำงานเมธอด method1() และเมื่อทำงานเมธอด method1() เสร็จแล้วก็จะถอนเมธอด method1() ออกจากแสต็กแล้วไปทำงานเมธอด main()
โดยปรกติโปรแกรมหรือโปรเซสจะมีเธรดเดียว โดยในเธรดเดียวกันโปรแกรมจะทำงานเรียงลำดับกันไป เมื่อทำงานใดงานหนึ่งเสร็จแล้วจึงจะสามารถทำงานถัดไปได้ หากงานที่กำลังทำอยู่ต้องใช้เวลานาน เช่น อ่านข้อมูลจำนวนมากจากฐานข้อมูล ตราบเท่าที่ยังอ่านข้อมูลไม่เสร็จ โปรแกรมส่วนถัดไปก็จะไม่ถูกทำงาน ซึ่งในมุมมองของผู้ใช้งานจะดูเหมือนว่าโปรแกรมค้างและไม่สามารถใช้งานอื่นได้ ดังนั้นไม่ว่าเราจะพัฒนาซอฟต์แวร์ขนาดเล็กๆหรือซอฟต์แวร์ขนาดใหญ่ เราย่อมต้องการให้ซอฟต์แวร์ทำงานได้อย่างรวดเร็วเพื่อส่งมอบประสบการณ์ที่ดีให้กับผู้ใช้งาน การปล่อยให้ส่วนของโปรแกรมที่ทำงานช้าดำเนินการจนเสร็จแล้วจึงไปทำงานอื่นต่อไปจึงไม่ใช่วิธีการที่ดี เพราะนอกจากจะทำให้ประสิทธิภาพของโปรแกรมโดยรวมลดลงแล้ว ยังทำให้ผู้ใช้งานรู้สึกไม่ดีกับซอฟต์แวร์ ในกรณีเช่นนี้มีเทคนิกที่ถูกนำมาใช้เพื่อเพิ่มประสิทธิภาพในการทำงานของโปรแกรมคือการเขียนโปรแกรมแบบ concurrency และการเขียนโปรแกรมแบบ asynchronous
สำหรับภาษาโปรแกรมที่รองรับการทำหลายเธรดได้จะใช้การเขียนโปรแกรมแบบ concurrency ซึ่งก็คือการเขียนโปรแกรมเพื่อสร้างเธรดขึ้นมามากกว่าหนึ่งเธรด โดยแต่ละเธรดจะรองรับการทำงานส่วนของโปรแกรมที่แตกต่างกันไป เพื่อให้แต่ละส่วนของโปรแกรมสามารถทำงานได้พร้อมๆกัน นั่นหมายถึงหลายๆฟังก์ชั่นหรือเมธอดสามารถทำงานได้พร้อมๆกัน การเขียนโปรแกรมแบบ concurrency เป็นงานที่ท้าทายเพราะฟังก์ชั่นหรือเมธอดที่ทำงานพร้อมกันจะใช้พื้นที่หน่วยความจำเดียวกันในการเก็บ เขียน และอ่าน ทั้งออบเจกต์และตัวแปร เพราะทุกๆเธรดอยู่ในโปรเซสเดียวกัน ซึ่งอาจจะนำมาซึ่งปัญหาการสลับกันทำงานแบบแปลกๆ (odd interleaving effects ) รวมถึงปัญหาอย่าง เช่น deadlock, live lock และ resource starvation แต่ถึงแม้การเขียนโปรแกรมแบบ concurrency จะเป็นงานที่ท้าทายแต่ก็ช่วยให้ประสิทธิภาพการทำงานของโปรแกรมดีขึ้น ยิ่งในปัจจุบัน cpu มีการเพิ่ม core มากขึ้น ยิ่งทำให้เหมาะกับการใช้โปรแกรมแบบ concurrency มากขึ้น ตัวอย่างของภาษาโปรแกรมที่รองรับการเขียนโปรแกรมแบบ concurrency เช่น ภาษาจาวาที่รองรับการเขียนโปรแกรมให้ทำงานแบบหลายเธรด โดยเราสามารถสร้างเธรดขึ้นมาเพื่อให้เมธอดที่คาดว่าจะทำงานช้าหรือค่อนข้างอ่อนไหวมีเธรดแยกต่างหากของตัวเองเพื่อไม่ให้โปรแกรมโดยรวมต้องหยุดรอจนกว่าเมธอดที่ทำงานช้าจะทำงานเสร็จ
ในภาษาจาวาเราสามารถสร้างเธรดได้ 2 วิธีคือสร้างเป็นคลาสที่สืบทอด (subclass) จากคลาส Thread แล้วโอเวอร์ไรด์เมธอด run() เพื่อให้ทำงานตามที่เราต้องการซึ่งทำได้ 2 แบบคือใช้คลาสที่ระบุชื่อ (named class – คลาสพื้นฐานตามที่เราใช้งานทั่วไป) หรือใช้งานในแบบคลาสที่ไม่ระบุชื่อ (anonymous class) ดังตัวอย่างด้านล่าง
หรือหากเราต้องการใช้แค่เมธอด run() เท่านั้นเราสามารถสร้างเธรดได้โดยการ implement คลาสอินเตอร์เฟส Runnable ให้กับคลาสใดๆก็ได้แล้วโอเวอร์ไรด์เมธอด run() เพื่อให้ทำงานตามที่เราต้องการ โดยสามารถทำได้ทั้งแบบการใช้คลาสที่ระบุชื่อหรือคลาสที่ไม่ระบุชื่อเช่นเดียวกัน ดังตัวอย่างด้านล่างเป็นการใช้งานสำหรับการใช้คลาสที่ระบุชื่อและวิธีการเรียกใช้ในคลาส Main
สำหรับภาษาโปรแกรมที่ไม่รองรับการทำหลายเธรดอย่างเช่น ภาษาไทป์สคริปต์ (Typescript) และภาษาจาวาสคริปต์ (Javascript) จะใช้เทคนิก concurrency ไม่ได้ ดังนั้นจะใช้
เทคนิก asynchronous callback function แทน โดยภาษาโปรแกรมจะต้องมีความสามารถในการทำ non-blocking และ callback function โดย non-blocking คือการยอมให้โปรแกรมอื่นทำงานแม้ว่าโปรแกรมที่กำลังทำงานอยู่จะยังทำงานไม่เสร็จ ส่วน callback function เป็นการส่งฟังก์ชันผ่านพารามิเตอร์ของฟังก์ชันอื่นเพื่อที่จะถูกเรียกใช้ (callback) เมื่อฟังก์ชั่นนั้นๆทำตามเงื่อนไขที่กำหนดเสร็จสิ้น
calback function จะมีทั้งแบบ synchronous callback function และ asynchronous callback function ตัวอย่างของ synchronous callback function เช่น การใช้ฟังก์ชั่น forEach เพื่อเรียงลำดับข้อมูลชนิด String ในอาเรย์ ซึ่งฟังก์ชั่นที่ทำงานคือ forEach ซึ่งเมื่อสำรวจไปในแต่ละสมาชิกจะเรียก callback fucntion ซึ่งในที่นี้เป็น anonymous function เพื่อพิมพ์ดัชนีและชื่อออกมา
const name = [‘Somchai’, ‘Teera’, ‘Aranya’ ];
name.forEach(function(name, index){
console.log(‘#’+index+’->’+name);
};
สำหรับตัวอย่างของ asynchronous callback function เช่น การใช้ฟังก์ชั่น SetTimeout(onDone, delay); ซึ่ง SetT imeout เป็นฟังก์ชั่นแบบ asynchronous callback ที่เข้าใจง่ายและถูกใช้ค่อนข้างบ่อย SetTimeout เป็นฟังก์ชั่นที่รับพารามิเตอร์ 2 ตัวคือ onDone และ delay โดยพารามิเตอร์ onDone คือ callback function ที่จะถูกเรียกมาทำงานหลังจากหยุดคอยจนครบตามเวลาตามที่กำหนดไว้ที่พารามิเตอร์ delay จากตัวอย่างด้านล่าง เมื่อเราเรียกใช้ฟังก์ชั่น SetTimeout มันจะหยุดคอย 3000 มิลลิวินาที จากนั้นจึงทำฟังก์ชั่นที่กำหนดไว้คือพิมพ์คำว่า Hello World ซึ่งในช่วงที่หยุดคอยนี้โปรแกรมจะสามารถไปทำงานอื่นได้ด้วยความสามารถของการทำ non-blocking แล้วจึงกลับมาทำงานตามฟังก์ชั่นที่ต้องไปทำต่อเมื่อครบตามเวลา
SetTimeout( function() {
console.log(‘Hello World’);
} , 3000);
อย่างไรก็ตามการใช้ asynchronous callback function จำเป็นต้องอาศัยความสามารถของแพลตฟอร์มที่โปรแกรมทำงานอยู่ เช่น การใช้งานบนบราวเซอร์ หรือ การใช้งานบนโหนด เช่น node.js โดยกลไกของแพลตฟอร์มจะแบ่งการทำงานออกเป็น call stack, web api, event loop และ callback queue ซึ่งส่วนต่างๆเหล่านี้จะทำงานร่วมกัน ในกรณีทั่วไปการทำงานของโปรแกรมจะถูกควบคุมด้วย call stack แต่ถ้าเป็นการทำงานกับส่วนของโปรแกรมแบบ asynchronous จะถูกควบคุมโดยส่วนของ web api ซึ่งควบคุมโดยบราวเซอร์หรือโหนด ดังนั้นแต่ละส่วนของโปรแกรมจะทำงานไปพร้อมๆกันทั้งส่วนที่อยู่ใน call stack และส่วนที่อยู่ใน web api และเมื่อส่วนของโปรแกรมที่เป็น asynchronous ใน web api ทำงานเสร็จจะเตรียมการทำงานในส่วนของ callback function โดยย้ายการทำงานจาก web api มาเข้าคิวรอที่ callback queue โดยมี event loop คอยตรวจสอบว่าหาก call stack ว่างก็จะย้ายงานที่รอใน callback queue ไปยัง call stack เพื่อทำงานต่อไป ในการใช้เทคนิก asynchronous callback function โปรแกรมจะต้องเขียนโดยใช้ไลบรารี่ที่เป็น asynchronous callback function
ข้อด้อยของการใช้ asynchronous callback function คือ หากมีการใช้งาน asynchronous callback function ซ้อนกันหลายๆชั้น (callback nest) การจะทำความเข้าใจว่าโปรแกรมทำงานอย่างไรจากการอ่านเนื้อหาของโปรแกรมจะค่อนข้างยากต่อการทำความเข้าใจว่าอะไรจะเกิดขึ้นเมื่อโปรแกรมทำงานจริง และในการใช้งาน asynchronous callback function เราไม่สามารถใช้ความสามารถในการตรวจจับข้อผิดพลาด (exception handling) ของภาษาโปรแกรมได้ ดังนั้นจึงมีการพัฒนามาใช้ออบเจกต์ promise เพื่อจัดการการทำงานแบบ asynchronous แทนการใช้ asynchronous callback function
ออบเจกต์ promise เป็นออบเจกต์ที่แสดงสถานะของการทำงานแบบ asynchronous ว่าสำเร็จหรือไม่สำเร็จ ออบเจกต์ promise จะมี 2 สถานะคือ pending และ settled โดยสถานะ settled จะแบ่งออกเป็น fullfilled และ rejected การเปลี่ยนสถานะของออบเจกต์ promise จะเปลี่ยนได้ทิศทางเดียวจากคือจาก pending ไปเป็น fullfilled หรือ rejected เท่านั้น
ตัวอย่างด้านล่างเป็นการเรียกใช้ ออบเจกต์ promise ผ่านเมธอด promiseMethod
promiseMethod(parameter).then(callback function).catch(error)
เมื่อเมธอด promiseMethod ทำงาน ถ้าการทำงานสำเร็จจะย้ายไปทำงานต่อตามที่กำหนดใน .then เพื่อไปทำงานต่อตามฟังก์ชั่นที่กำหนด แต่หากเมธอด promiseMethod มีการแจ้งข้อผิดพลาดกลับมา จะย้ายไปทำงานต่อตามที่กำหนดใน .catch เพื่อดักจับและจัดการข้อผิดพลาด
จะเห็นว่า .then ก็คือส่วนที่เป็น fullfilled และ .catch ก็คือส่วนที่เป็น rejected ดังนั้นจะเห็นว่าการใช้ออบเจกต์ promise จะจบในคำสั่งเดียวทำให้เข้าใจง่าย และถึงแม้จะนำมาซ้อนกัน (nest) ดังตัวอย่างด้านล่างก็ยังอ่านทำความเข้าใจได้ง่ายอยู่ และยังสามารถใช้ความสามารถในการจัดการข้อผิดพลาดที่มีมากับภาษาโปรแกรมได้ด้วย ในการใช้ออบเจกต์ promise โปรแกรมของเราจะต้องเขียนโดยใช้ไลบรารี่ที่เป็น promise
doSomething(function (result) {
doSomethingElse(result, function (newResult) {
doThirdThing(newResult, function (finalResult) {
console.log(`Got the final result: ${finalResult}`);
}, failureCallback);
}, failureCallback);
}, failureCallback);