Callback function ในภาษา JavaScript

10 September 2020

ในบทนี้ คุณจะได้เรียนรู้เกี่ยวกับ Callback function (ฟังก์ชัน Callback) ในภาษา JavaScript เราจะแนะนำให้คุณรู้จักว่าฟังก์ชัน Callback คืออะไร และมันถูกนำไปใช้อย่างไรในการเขียนโปรแกรม นี่เป็นเนื้อหาในบทนี้

  • Callback function คืออะไร
  • Function expression as callback
  • การส่งค่ากลับไปยัง Callback function
  • ฟังก์ชันที่มีหลาย Callback function
  • setTimeout example
  • Array callback example

Callback function คืออะไร

Callback function หรือฟังก์ชันเรียกกลับ คือฟังก์ชันที่ถูกส่งเป็นพารามิเตอร์ของฟังก์ชันอื่นเพื่อเรียกใช้งานในภายหลัง ฟังก์ชัน Callback นั้นมีประโยชน์เมื่อเราสร้างฟังก์ชันและต้องการให้ส่วนที่เรียกใช้งานฟังก์ชันสามารถกำหนดการทำงานบางอย่างเองได้ โดยการทำงานนั้นจะกำหนดผ่านฟังก์ชัน Callback

นี่เป็นตัวอย่างของการใช้งานฟังก์ชัน Callback เพื่อให้ส่วนที่เรียกใช้งานฟังก์ชันสามารถทำงานบางอย่างหลังจากที่ฟังก์ชัน hello ทำงานเสร็จสิ้น

callback.js
function success() {
    console.log("success called");
}

function hello(cb) {
    console.log("hello called");
    cb();
}

hello(success);

นี่เป็นผลลัพธ์การทำงานของโปรแกรม

hello called
success called

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

hello(success);

ในกรณีนี้ จะกล่าวได้ว่าฟังก์ชัน success นั้นเป็นฟังก์ชัน Callback ของฟังก์ชัน hello เนื่องจากมันถูกส่งเป็นพารามิเตอร์และถูกนำไปเรียกใช้งานภายในฟังก์ชัน hello หรือกล่าวอีกนัยหนึ่งฟังก์ชัน hello เรียกกลับ (Callback) มายังฟังก์ชัน success เมื่อมันทำงานเสร็จนั่นเอง

Function expression as callback

นอกจากนี้ เรายังสามารถส่ง Callback function ในรูปแบบ Function expression หรือ Arrow function ได้โดยไม่จำเป็นต้องประกาศฟังก์ชันก่อน นี่สามารถช่วยอำนวยความสะดวกในการเขียนโปรแกรมเป็นอย่างมาก เนื่องจากฟังก์ชัน Callback มักจะถูกสร้างขึ้นมาเพื่อใช้งานเพียงครั้งเดียว

function_expression.js
function sendMessage(to, message, callback) {
    console.log("To " + to + ": " + message);
    callback();
}

sendMessage("John", "Hello John", function () {
    console.log("Message has been sent");
});

sendMessage("John", "How are you?", () => {
    console.log("Hooking notification service");
});

และนี่เป็นผลลัพธ์การทำงานของโปรแกรม

To John: Hello John
Message has been sent
To John: How are you?
Hooking notification service

ในตัวอย่าง เป็นการส่งฟังก์ชัน Callback ด้วยการใช้ Function expression และ Arrow function และโดยทั่วไปแล้วเรามักจะส่งฟังก์ชัน Callback ด้วยวิธีนี้ เนื่องจากเราสามารถส่งมันได้ในทันทีโดยไม่ต้องประกาศฟังก์ชันเอาไว้ก่อน

function sendMessage(to, message, callback) {
    console.log("To " + to + ": " + message);
    callback();
}

ฟังก์ชัน sendMessage มีพารามิเตอร์เป็นฟังก์ชัน Callback callback ที่จะถูกเรียกใช้งานเมื่อการส่งข้อความเสร็จสิ้น ในตัวอย่างนี้แสดงให้เห็นว่าฟังก์ชัน sendMessage อาจถูกเรียกใช้งานจากหลายส่วนของโปรแกรม และแต่ละส่วนก็สามารถกำหนดการทำงานที่แตกต่างกันเมื่อการส่งข้อความเสร็จสิ้น จากฟังก์ชัน Callback ที่ส่งเข้ามา

การส่งค่ากลับไปยัง Callback function

เนื่องจาก Callback function นั้นเป็นฟังก์ชัน ดังนั้นมันสามารถมีพารามิเตอร์เหมือนกับฟังก์ชันทั่วไป นั่นทำให้เราสามารถส่งค่ากลับมายังฟังก์ชัน Callback ในตอนเรียกกลับได้ การทำงานเช่นนี้สามารถพบเห็นได้ทั่วไปในฟังก์ชันหรือเมธอดของภาษา JavaScript เองที่รับพารามิเตอร์เป็นฟังก์ชัน Callback

ในตัวอย่างนี้ เป็นฟังก์ชันสำหรับหาค่า ห.ร.ม (GCD) ของตัวเลขจำนวนเต็มสองตัว ส่งและผลลัพธ์กลับมาผ่านฟังก์ชัน Callback เมื่อการทำงานเสร็จสิ้น

gcd_callback.js
function gcd(a, b, success) {
    let result;
    let min = a < b ? a : b;
    for (let i = min; i >= 1; i--) {
        if (a % i == 0 && b % i == 0) {
            result = i;
            break;
        }
    }
    // Call function and send result back
    success(result);
}

gcd(6, 15, function (result) {
    console.log("GCD = " + result);
});

gcd(16, 24, function (result) {
    console.log("Finding GCD of 16 and 24");
    console.log("Result: " + result);
});

ในตัวอย่าง ฟังก์ชัน gcd เป็นฟังก์ชันสำหรับหาค่า ห.ร.ม (GCD) ของตัวเลขจำนวนเต็มสองตัว ฟังก์ชันมีพารามิเตอร์ success ซึ่งเป็นฟังก์ชัน Callback ที่จะถูกเรียกใช้งานและส่งผลลัพธ์ค่าของ ห.ร.ม ที่ได้กลับไป

gcd(6, 15, function (result) {
    console.log("GCD = " + result);
});

gcd(16, 24, function (result) {
    console.log("Finding GCD of 16 and 24");
    console.log("Result: " + result);
});

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

จะเห็นว่าเราสามารถส่งค่าจากฟังก์ชัน gcd กลับมายังฟังก์ชัน Callback ได้ และแต่ละฟังก์ชันก็นำผลลัพธ์ที่ได้ไปใช้แตกต่างกัน และนี่เป็นตัวอย่างของการใช้งานฟังก์ชัน Callback ที่พบได้ทั่วไปในภาษา JavaScript

ต่อมาเป็นตัวอย่างฟังก์ชันสำหรับวนรอบอาเรย์โดยการประยุกต์ใช้งานฟังก์ชัน Callback นี่เป็นโค้ดของโปรแกรม

array_callback.js
function iterateArray(array, callback) {
    for (let i = 0; i < array.length; i++) {
        callback(array[i], i);
    }
}

let numbers = [10, 20, 30, 40, 50];
let names = ["Chris", "Metin", "John", "Nathan"];

console.log("Diplay indexes and values");
iterateArray(numbers, function (value, index) {
    console.log("index: " + index, "value: " + value);
});

console.log("Length of array elements");
iterateArray(names, function(value, index) {
    console.log(value + " -> " + value.length);
});

นี่เป็นผลลัพธ์การทำงานของโปรแกรม

Diplay indexes and values
index: 0 value: 10
index: 1 value: 20
index: 2 value: 30
index: 3 value: 40
index: 4 value: 50
Length of array elements
Chris -> 5
Metin -> 5
John -> 4
Nathan -> 6

ในตัวอย่างนี้ เราได้สร้างฟังก์ชัน iterateArray สำหรับวนรอบข้อมูลในอาเรย์ โดยฟังก์ชันรับพารามิเตอร์สองตัวคือ อาเรย์ที่ต้องการวนรอบและฟังก์ชัน Callback เพื่อทำงานกับสมาชิกแต่ละตัวของอาเรย์ในการวนแต่ละรอบ

function iterateArray(array, callback) {
    for (let i = 0; i < array.length; i++) {
        callback(array[i], i);
    }
}

ในการทำงานของฟังก์ชันนั้นจะวนอาเรย์ด้วยคำสั่ง for loop และเรียกใช้งานฟังก์ชัน Callback โดยส่งค่าในอาเรย์และ Index กลับไปยังฟังก์ชัน Callback นั่นหมายความว่าถ้าหากอาเรย์มีสมาชิก N ตัวฟังก์ชัน Callback จะถูกเรียกใช้งานเป็นจำนวน N ครั้ง

let numbers = [10, 20, 30, 40, 50];
let names = ["Chris", "Metin", "John", "Nathan"];

เราได้ประกาศตัวแปรอาเรย์สองตัวที่เป็นตัวเลขและ String สำหรับใช้งานกับฟังก์ชัน iterateArray ที่เราได้สร้างขึ้นมา

iterateArray(numbers, function (value, index) {
    console.log("index: " + index, "value: " + value);
});

...
iterateArray(names, function(value, index) {
    console.log(value + " -> " + value.length);
});

จากนั้นเรียกใช้ฟังก์ชันโดยส่งอาเรย์และฟังก์ชัน Callback เพื่อทำงานกับสมาชิกแต่ละตัวในอาเรย์ จะเห็นว่าเราสามารถใช้ฟังก์ชัน iterateArray เพื่อวนรอบอาเรย์โดยไม่จำเป็นต้องเขียนคำสั่ง for loop ทุกครั้ง แต่ทำงานผ่านฟังก์ชัน Callback ที่จะถูกเรียกใช้ในแต่ละรอบที่คำสั่ง for วนรอบอาเรย์แทน

ซึ่งนี่เป็นตัวอย่างของการใช้งานฟังก์ชัน Callback ที่พบในอาเรย์เมธอดของภาษา JavaScript เอง และเราจะพูดถึงมันหลังจากนี้

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

benchmarking_callback.js
function benchmarking(measureFn) {
    let start = Date.now();
    measureFn();
    return (Date.now() - start) / 1000;
}

function frequencyOfPrime() {
    let n = 100000;
    let i, j;
    let freq = n - 1;
    for (i = 2; i <= n; ++i) {
        for (j = Math.sqrt(i); j > 1; --j) {
            if (i % j == 0) { --freq; break; }
        }
    }
    return freq;
}

console.log("Calculating...");
let sec = benchmarking(frequencyOfPrime);
console.log("It took %d seconds to find primes lower than 100,000.", sec);

นี่เป็นผลลัพธ์การทำงานของโปรแกรม

Calculating...
It took 0.201 seconds to find primes lower than 100,000.

ในตัวอย่าง เป็นโปรแกรมสำหรับจับเวลาการทำงานของโปรแกรม เราใช้มันสำหรับจับเวลาว่าการค้นหาตัวเลขจำนวนเฉพาะที่มีค่าอยู่ระหว่าง 1 - 100,000 นั้นใช้เวลาเท่าไหร่

function benchmarking(measureFn) {
    let start = Date.now();
    measureFn();
    return (Date.now() - start) / 1000;
}

นี่เป็นฟังก์ชัน benchmarking มันใช้สำหรับจับเวลาการทำงานของฟังก์ชันใดๆ ที่ถูกส่งเป็นฟังก์ชัน Callback เข้ามาผ่านพารามิเตอร์ measureFn ในกรณีนี้ เราส่งฟังก์ชัน frequencyOfPrime สำหรับจับเวลาการทำงานของมัน

ภายในฟังก์ชันนั้นได้เก็บเวลาเริ่มต้นในตัวแปร start เพื่อเริ่มจับเวลา เราใช้เมธอด Date.now เพื่อรับเอาค่าเวลาปัจจุบันจากระบบ และเรียกใช้งานฟังก์ชัน Callback และเมื่อการทำงานเสร็จสิ้น ส่งค่ากลับเป็นเวลาที่วัดได้ในหน่วยวินาที

let sec = benchmarking(frequencyOfPrime);

จากนั้นเรียกใช้งานฟังก์ชันเพืืื่อวัดเวลาการค้นหาตัวเลขจำนวนเฉพาะจาก 1 - 100,000 จะเห็นว่าฟังก์ชัน Callback measureFn ไม่ได้รันที่ตอนท้ายของฟังก์ชัน benchmarking แต่มันถูกรันในระหว่างการจับเวลาเริ่มต้นและสิ้นสุด

ฟังก์ชันที่มีหลาย Callback function เป็นพารามิเตอร์

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

ในตัวอย่างนี้ ฟังก์ชันรับพารามิเตอร์เป็นสองฟังก์ชัน Callback ที่จะถูกเรียกก่อน และหลังจากที่ฟังก์ชันทำงานเสร็จ

sum_callback.js
function sum(a, b, before, after) {
    before(a, b);
    let sum = a + b;
    after(a, b, sum);
}

sum(1, 2, function (a, b) {
    console.log("Summation of two numbers %d and %d", a, b);
}, function (a, b, sum) {
    console.log("%d + %d = %d", a, b, sum);
});

sum(2, 3, function (a, b) {
    console.log("Does 2 + 3 equal to 5?");
}, function (a, b, sum) {
    if (sum == 5) {
        console.log("Yes");
    } else {
        console.log("No");
    }
});

นี่เป็นผลลัพธ์การทำงานของโปรแกรม

Summation of two numbers 1 and 2
1 + 2 = 3
Does 2 + 3 equal to 5?
Yes

ในตัวอย่างนี้ เราได้ทำให้ฟังก์ชันสำหรับหาผลรวมของตัวเลขมีความพิเศษมากขึ้น โดยการทำให้มันสามารถรับฟังก์ชัน Callback ที่จะทำงานก่อนและหลังจากที่ฟังก์ชัน sum หาผลรวมของตัวเลขเสร็จแล้ว

function sum(a, b, before, after) {
    before(a, b);
    let sum = a + b;
    after(a, b, sum);
}

ฟังก์ชันมีพารามิเตอร์สี่ตัว สองพารามิเตอร์แรก a และ b เป็นตัวเลขที่ต้องการหาผลรวม และสองพารามิเตอร์ที่เหลือเป็นฟังก์ชัน Callback โดยฟังก์ชัน before จะถูกเรียกใช้ก่อนการหาผลรวม และฟังก์ชัน after จะถูกเรียกใช้หลังจากที่หาผลรวมเสร็จแล้ว

sum(1, 2, function (a, b) {
    console.log("Summation of two numbers %d and %d", a, b);
}, function (a, b, sum) {
    console.log("%d + %d = %d", a, b, sum);
});

sum(2, 3, function (a, b) {
    console.log("Does 2 + 3 equal to 5?");
}, function (a, b, sum) {
    if (sum == 5) {
        console.log("Yes");
    } else {
        console.log("No");
    }
});

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

setTimeout example

ฟังก์ชันและเมธอดจำนวนมากในภาษา JavaScript รับพารามิเตอร์เป็น Callback function เพื่อนำไปทำงาน ยกตัวอย่างเช่น ฟังก์ชัน setTimeout รับพารามิเตอร์เป็นฟังก์ชัน Callback และนำไปเรียกใช้งานหลังจากที่เวลาผ่านไปตามระยะเวลาที่กำหนด นี่เป็นตัวอย่าง

settimeout1.js
console.log("Program started");
console.log("Waiting for 3 seconds...");

setTimeout(function () {
    console.log("Hello World!");
}, 3000);

นี่เป็นผลลัพธ์การทำงานของโปรแกรม

Program started
Waiting for 3 seconds...
Hello World!

ในตัวอย่าง เป็นการใช้งานฟังก์ชัน setTimeout สำหรับหน่วงเวลาการทำงานของโปรแกรมตามระยะเวลาที่กำหนด ฟังก์ชันนี่รับพารามิเตอร์สองตัวคือฟังก์ชัน Callback ที่จะถูกเรียกใช้งานหลังจากที่ผ่านเวลาไป 3000 มิลลิวินาทีหรือ 3 วินาที ที่ส่งเป็นพารามิเตอร์ที่สองของฟังก์ชัน

การทำงานของฟังก์ชัน setTimeout เป็นแบบไม่รอ (Asynchronous) นั่นหมายความว่าฟังก์ัชัน Callback จะถูกเรียกใช้งานหลังจากที่ Script หลักของโปรแกรมจบลง ดังนั้นถ้าคุณมีคำสั่งอื่นหลังจากการเรียกใช้ฟังก์ชัน มันจะทำงานในทันที ยกตัวอย่างเช่น

settimeout2.js
console.log("Program started");

setTimeout(function () {
    console.log("3 seconds passed after the script ended");
    console.log("Hello World!");
}, 3000);

console.log("Program ended");

นี่เป็นผลลัพธ์การทำงานของโปรแกรม

Program started
Program ended
3 seconds passed after the script ended
Hello World!

จะเห็นว่าฟังก์ชัน Callback ทำงานหลังจากที่ Script หลักของโปรแกรมทำงานเสร็จ ซึ่งนี่เป็นการทำงานของ Event loop ในภาษา JavaScript และมันไม่ได้ครอบคลุมในบทเรียนนี้

Array callback example

นอกจากนี้เมธอดของอาเรย์ในภาษา JavaScript หลายเมธอดยังทำงานกับ Callback function เมธอดเหล่านี้รับ Callback function เป็นพารามิเตอร์เพื่อทำงานบางอย่าง มาดูตัวอย่างการใช้งานเมธอดของอาเรย์ที่ใช้งานบ่อยๆ นี่เป็นตัวอย่างของการใช้เมธอด map เพื่อแปลงค่าในอาเรย์

let numbers = [1, 2, 3, 4, 5, 6];
numbers = numbers.map(function (item) {
    return item * 2;
});
console.log("Doubled: ", numbers);

นี่เป็นผลลัพธ์การทำงานของโปรแกรม

Doubled:  [ 2, 4, 6, 8, 10, 12 ]

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

return item * 2;

และสิ่งที่เราส่งค่ากลับจากฟังก์ชัน Callback จะกลายมาเป็นค่าใหม่ของอาเรย์ ในตัวอย่างเราได้คูณค่าของตัวเลขด้วย 2 เพื่อทำให้มันมีค่าเป็นสองเท่านั่นเอง

ตัวอย่างถัดมาเป็นการใช้งานเมธอด forEach สำหรับวนแต่ละสมาชิกในอาเรย์

let languages = ["PHP", "Ruby", "Python", "JavaScript", "GO"];
languages.forEach(function(value, index) {
    console.log(index, value);
});

นี่เป็นผลลัพธ์การทำงานของโปรแกรม

0 PHP
1 Ruby
2 Python
3 JavaScript
4 GO

ในตัวอย่าง เป็นโปรแกรมสำหรับวนรอบอาเรย์ด้วยเมธอด forEach เมธอดนี้รับพารามิเตอร์เป็นฟังก์ชัน Callback; การทำงานของมันคือวนแต่ละค่าในอาเรย์จากนั้นส่งค่าและ Index เข้าไปยังฟังก์ชัน Callback และแสดงค่าทั้งสองออกมาทางหน้าจอ ซึ่งนี่จะคล้ายกับตัวอย่างก่อนหน้าในฟังก์ชัน iterateArray

อย่างไรก็ตาม เราสามารถใช้คำสั่ง for loop สำหรับการทำงานนี้ได้ แต่ในกรณีนี้การใช้เมธอด forEach อาจสะดวกกว่าเนื่องจากเราต้องการใช้ค่าและ Index ของอาเรย์และเมธอด forEach ถูกออกแบบมาสำหรับทำงานเช่นนี้

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

บทความนี้เป็นประโยชน์หรือไม่?Yes·No