คำสั่ง async/await ในภาษา JavaScript

24 May 2021

ในบทนี้ คุณจะได้เรียนรู้การใช้งานคำสั่ง async/await ในภาษา JavaScript ที่ใช้สำหรับเปลี่ยนการทำงานของ Promise ให้เป็นแบบ Synchronous ซึ่งมันสามารถช่วยลดการใช้งานของฟังก์ชัน Callback และทำให้โค้ดอ่านง่ายและเป็นระเบียบมากขึ้น นี่เป็นเนื้อหาในบทนี้

  • Async function
  • การใช้งานคำสั่ง Await
  • Handing promise rejection
  • ตัวอย่าการใช้งานคำสั่ง async/await

Async function

คำสั่ง async เป็นคำสั่งที่ใช้กำหนดให้กับฟังก์ชันในภาษา JavaScript เพื่อบ่งบอกว่าฟังก์ชันส่งค่ากลับเป็น Promise ซึ่งเป็นออบเจ็คของการทำงานแบบ Asynchronous ดังนั้นการที่ฟังก์ชันถูกประกาศด้วยคำสั่ง async จะทำให้มันเป็นฟังก์ชัน Asynchronous โดยปริยาย นี่เป็นรูปแบบการใช้งาน

async function fn() {
    return promise;
}

ในรูปแบบการใช้งาน เราระบุคำสั่ง async หน้าฟังก์ชันในตอนที่ประกาศเพื่อทำให้มันเป็นฟังก์ชัน Asynchronous โดยค่าที่ส่งกลับจากฟังก์ชันจะต้องเป็นออบเจ็คของ Promise เสมอ และมันยังสามารถใช้กับ Arrow function ได้เช่นกัน

const fb = async () => {
    return promise;
}

นี่ให้ผลลัพธ์การทำงานที่เหมือนกัน แต่เราใช้ Arrow function ในการประกาศฟังก์ชันแทน นั่นหมายความว่าคุณสามารถเลือกใช้แบบไหนก็ได้ที่ต้องการ

อย่างที่ได้บอกไปก่อนหน้า ฟังก์ชันที่เป็น async จะต้องส่งค่ากลับเป็น Promise ออบเจ็คเสมอ ซึ่งนี่เป็นข้อบังคับ ในตัวอย่างนี้ เป็นการประกาศฟังก์ชันที่ส่งค่ากลับเป็น Promise ที่ resolve ค่าเป็นข้อความ "Hello"

async function myFunction() {
    return new Promise((resolve, reject) => {
        resolve('Hello');
    });
}

// Or

async function myFunction() {
    return Promise.resolve('Hello');
}

และนอกจากนี้ คุณยังสามารถละเว้นการสร้างออบเจ็ค Promise ที่ใช้ในการ resolve ค่าได้ นั่นคือเพียงแค่ส่งค่าที่ต้องการจากฟังก์ชันได้ทันที ยกตัวอย่างเช่น

async function myFunction() {
    return 'Hello';
}

ในกรณีนี้ เมื่อข้อมูลที่ส่งกลับจากฟังก์ชันไม่ใช่ Promise ภาษา JavaScript จะสร้าง Promise ที่ resolve ค่าดังกล่าวให้กับเราอัตโนมัติ ดังนั้นการส่งค่ากลับทั้งสามรูปแบบก่อนหน้าได้ผลลัพธ์เป็น Promise ที่ resolve ค่าเป็น "Hello" เช่นเดียวกัน นั่นหมายความว่าในการประกาศฟังก์ชันด้วยคำสั่ง async จะช่วยให้เราแน่ใจได้ว่าฟังก์ชันจะส่งค่ากลับเป็น Promise เสมอ

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

greeting.js
async function greet(name) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(`Hello ${name}!`);
        }, 1000);
    });
}

greet('Metin').then((value) => {
    console.log(value);
});

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

Hello Metin!

ฟังก์ชัน greet เป็นฟังก์ชัน Async ที่ส่งค่ากลับเป็นคำกล่าวทักทายชื่อที่ส่งเข้ามายังฟังก์ชันเมื่อเวลาผ่านไป 1 วินาที จะเห็นว่าฟังก์ชันส่งค่ากลับเป็นออบเจ็ค Promise ที่เรียกใช้งานฟังก์ชัน setTimeout เพื่อหน่วงเวลาการทำงานของโปรแกรม และทำการ resolve ค่าหลังจากที่เวลาผ่านไป 1 วินาที

greet('Metin').then((value) => {
    console.log(value);
});

เนื่องจากค่าที่ส่งกลับจากฟังก์ชันเป็น Promise นั่นทำให้เราเรียกใช้เมธอด then เพื่อรับเอาค่าที่ resolve มาใช้งานได้และแสดงมันออกทางหน้าจอ ส่วนในกรณีที่ Promise มีการ reject คุณสามารถใช้เมธอด catch เพื่อจัดการกับข้อผิดพลาดที่เกิดขึ้นได้ ซึ่งนี่ก็เป็นการทำงานพื้นฐานของ Promise อยู่แล้ว

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

การใช้งานคำสั่ง Await

คำสั่ง await ใช้เพื่อเปลี่ยนการทำงานของ Promise ให้เป็นแบบ Synchronous หรือบล็อคการทำงานจนกว่า Promise จะทำงานเสร็จ (resolve หรือ reject) นี่เป็นรูปแบบใหม่ที่จะสามารถช่วยให้การเขียนโปรแกรมกับ Promise สะดวกและง่ายขึ้นโดยที่ไม่ต้องใช้ฟังก์ชัน Callback

นี่เป็นรูปแบบของการใช้งานคำสั่ง await ในภาษา JavaScript

let result = await promise;
// Or
let result = await asyncFn();

ในรูปแบบการใช้งาน เราสามารถใช้คำสั่ง await กับออบเจ็ค Promise หรือฟังก์ชัน Async ที่ส่งค่ากลับเป็น Promise ได้ นี่จะทำให้โปรแกรมรอจนกว่า Promise จะทำงานเสร็จสิ้น และค่าที่ resolve จาก Promise จะถูกส่งกลับโดยตรงจากการเรียกใช้ฟังก์ชัน

นั่นหมายความว่าเราไม่จำเป็นต้องใช้เมธอด then เพื่อรับค่าจาก Promise อีกต่อไป เพียงแค่ใช้คำสั่ง await หน้าฟังก์ชัน และรอรับค่าที่ resolve มาจาก Promise เหมือนกับที่การเรียกใช้ฟังก์ชันปกติที่มีการทำงานเป็นแบบ Synchronous อยู่แล้ว

และจากตัวอย่างก่อนหน้าของโปรแกรมกล่าวทักทายชื่อ สามารถนำมาเขียนใหม่ได้ด้วยการใช้คำสั่ง await นี่เป็นตัวอย่าง

greeting_v2.js
async function greet(name) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(`Hello ${name}!`);
        }, 1000);
    });
}

async function run() {
    let message = await greet('Metin');
    console.log(message);
}

run();

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

Hello Metin!

ในตัวอย่างนี้ เป็นโปรแกรมสำหรับกล่าวคำทักทายหลังจากเวลาผ่านไป 1 วินาทีเช่นเดิม แต่แทนที่จะใช้เมธอด then ของ Promise เพื่อรอรับค่าส่งกลับ เราใช้คำสั่ง await แทน จะเห็นว่าการทำงานของฟังก์ชัน greet จะเหมือนกับว่ามันเป็น Synchronous และใช่แล้วมันเป็น

let message = await greet('Metin');

ในบรรทัดนี้จะทำให้โปรแกรมรอจนกว่า Promise จะส่งค่ากลับ เราเพียงแค่ระบุคำสั่ง await หน้าฟังก์ชันที่เรียกใช้ และค่าที่ resolve จาก Promise จะถูกส่งกลับมาจากการเรียกใช้นี้ และถูกเก็บลงในตัวแปร message

อย่างไรก็ตาม เมื่อฟังก์ชันเป็น Async คุณต้องไม่ลืมที่จะใส่คำสั่ง await หน้าการเรียกใช้งานฟังก์ชันด้วยเสมอ ไม่เช่นนั้น ค่าที่ส่งกลับจะเป็น Promise จะไม่ใช่ค่าที่ resolve จาก Promise ยกตัวอย่างเช่น ถ้าหากคุณลืม

let message = greet('Metin');

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

Promise { <pending> }

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

หรือกล่าวอีกนัยหนึ่ง await ถูกออกแบบให้ใช้ร่วมกับฟังก์ชันที่ถูกประกาศด้วยคำสั่ง async ที่ส่งค่ากลับเป็น Promise ออบเจ็คเพื่อทำให้มันรอการทำงานจนกว่า Promise จะ resolve ค่ากลับ

อีกสิ่งหนึ่งที่สำคัญในการใช้งานคำสั่ง await ก็คือ เราได้สร้างฟังก์ชัน run ซึ่งเป็นฟังก์ชัน Async ที่ใช้สำหรับรันโปรแกรม เหตุผลที่ต้องทำเช่นนี้เพราะนี่เป็นข้อบังคับในภาษา JavaScript เนื่องจากว่าคำสั่ง await นั้นจะสามารถใช้ได้ในฟังก์ชัน Asynchronous หรือฟังก์ชันที่ถูกประกาศด้วยคำสั่ง async เท่านั้น

นั่นหมายความว่าคุณไม่สามารถใช้คำสั่ง await ได้ในระดับ Top level ของโปรแกรม มีิอีกวิธีที่เราสามารถใช้งานคำสั่ง await ได้อย่างรวดเร็วคือการสร้างฟังก์ชัน Anonymous ที่เป็นแบบ Async ครอบทั้งหมดของโปรแกรม ยกตัวอย่างเช่น

(async function () {
    // Now can use 'await' here
})();

Handing promise rejection

Promise ไม่เพียงแค่ส่งค่ากลับเป็นผลสำเร็จ (resolve) เท่านั้่น แต่มันยังสามารถส่งค่ากลับเป็นผลล้มเหลวได้ นั่นก็คืือ Promise ที่ถูก Reject นั่นเอง ซึ่งการ Reject สามารถเกิดได้จากข้อผิดพลาดใดๆ ที่เกิดขึ้นใน Promise ที่เกิดจากการ throw error หรือจากการเรียกใช้ฟังก์ชัน reject โดยตรง

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

myPromiseFn().then((value) => {
    // Done
}).catch((err) => {
    // Handing error
});

แต่เมื่อใช้คำสั่ง await กับ Promise ในการจัดการกับข้อผิดพลาดทำให้เราสามารถใช้คำสั่ง try catch ได้เลย เนื่องจากนี่เป็นการทำงานแบบ Synchronous ยกตัวอย่างเช่น

try {
    let value = await myPromiseFn();
    // Done
} catch (err) {
    // Handing error
}

ซึ่งง่ายกว่าที่คุณคิด นี่เป็นวิธีจัดการกับข้อผิดพลาดที่คุ้นเคยในภาษา JavaScript เนื่องจากจากโค้ดทำงานแบบ Synchronous แล้วนั่นเอง ดังนั้นสรุปได้ว่าการใช้คำสั่ง await ช่วยให้ไม่ต้องใช้ฟังก์ชัน then และ catch ของ Promise อีกต่อไป

ตัวอย่างการใช้งานคำสั่ง async/await

สำหรับตัวอย่างการใช้งานคำสั่ง async/await ในรูปแบบเต็ม จะเป็นการใช้งานแพ็กเกจ node-fetch สำหรับเรียกข้อมูลผ่านทาง HTTP เพื่อนำมาใช้งานในโปรแกรม ก่อนอื่นคุณจะต้องติดตั้งแพ็กเกจด้วย npm โดยคำสั่งต่อไปนี้

npm install node-fetch

นี่เป็นโมดูลที่ทำงานเหมือนกับ window.fetch API บนเว็บเบราว์เซอร์ แต่เนื่องจากบทเรียนนี่เป็น Node.js เราจึงต้องติดตั้งมันผ่าน npm ในตัวอย่างนี้ เราจะเขียนโปรแกรมเพื่อเรียกเอาข้อมูลจาก API นี้บนเว็บไซต์ Github: https://api.github.com/users/github

หากคุณมีบัญชีของคุณบน Github คุณสามารถเปลี่ยน API ให้รับข้อมูลบัญชีของคุณแทนโดยแทนที่ชื่อผู้ใช้เป็นของคุณเอง เช่น

https://api.github.com/users/{username}

โดย API นี้ใช้สำหรับรับเอาข้อมูลเกี่ยวกับบัญชีของ Github เอง เราสามารถใช้ฟังก์ชัน fetch เพื่อเรียกเอาข้อมูลจาก API ได้ดังนี้

node_fetch_example.js
const fetch = require('node-fetch');

fetch('https://api.github.com/users/github')
    .then(res => res.json())
    .then(json => console.log(json));

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

{
  login: 'github',
  id: 9919,
  node_id: 'MDEyOk9yZ2FuaXphdGlvbjk5MTk=',
  avatar_url: 'https://avatars.githubusercontent.com/u/9919?v=4',
  gravatar_id: '',
...

ฟังก์ชัน fetch เป็นฟังก์ชัน Asynchronous ที่ส่งค่ากลับเป็น Promise นั่นหมายความว่าเมื่อการเชื่อมต่อไปยัง Server สำเร็จ มันส่งค่ากลับเป็นออบเจ็คของ Promise ที่ resolve ค่าเป็นออบเจ็ค Response สำหรับการร้องขอ

.then(res => res.json())

ดังนั้นเราเรียกใช้เมธอด then เพื่อรับเอาออบเจ็คมาใช้งาน และในตอนนี้ค่าที่ส่งกลับมาจากออบเจ็ค Response นั้นมีเพียง Header เท่านั้น นั่นหมายความว่าเพื่ออ่านค่าของ Response body เราจะต้องเรียกใช้อีกเมธอดบนออบเจ็ค นั่นคือเมธอด res.json

.then(json => console.log(json));

เมธอด res.json จะส่งค่ากลับเป็น Promise ที่ resolve ข้อมูลของ Response body ในรูปแบบ JSON และเราเรียกใช้เมธอด then อีกครั้งเพื่อรับค่าสุดท้าย จะเห็นว่าเราได้ทำการเชื่อมต่อ Promise เนื่องจากมีมากกว่าหนึ่ง Promise ที่ทำงานต่อเนื่องกัน นั่นคือการเรียกใช้ฟังก์ชัน fetch และตามด้วยเมธอด res.json

แทนที่จะใช้เมธอด then และ catch และอย่างที่คุณรู้ เราสามารถใช้คำสั่ง await แทนได้ ดังนั้นในตัวอย่างนี้ เราสามารถใช้คำสั่ง async/await แทนได้ดังนี้

fetch_example.js
const fetch = require('node-fetch');

(async function () {
    // wait for http response
    let res = await fetch('https://api.github.com/users/github');
    // wait until response body is read
    let json = await res.json();
    // finally got value
    console.log(json)
})();

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

และในกรณีที่คุณต้องการรันโปรแกรมนี้บนเว็บเบราวน์เซอร์ เพีียงแค่ลบบรรทัดนี้ออก

const fetch = require('node-fetch');

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

ในบทเรียนนี้ คุณได้เรียนรู้เกี่ยวกับคำสั่ง async/await ที่ออกแบบมาสำหรับใช้งานร่วมกับ Promise เพื่อเปลี่ยนการทำงานเป็นแบบ Synchronous นี่ช่วยให้การทำงานกับ Promise สามารถทำได้ง่ายขึ้นในกรณีที่มันมีการทำงานต่อเนื่องกันแบบเป็นลำดับ

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