การรับค่าและการแสดงผล ในภาษา JavaScript

4 August 2020

ในบทนี้ คุณจะได้เรียนรู้เกี่ยวกับการรับค่าและการแสดงผลในภาษา JavaScript; ภาษา JavaScript นั้นเป็นภาษาที่รันอยู่บน Host environment และไม่มีฟังก์ชันสำหรับการรับค่าเป็นของมันเอง ในบทนี้เราจะพูดถึงการรับค่าและแสดงผลบน Command line ที่รันบน Node.js เท่านั้น เราจะไม่พูดถึงการรับค่าและแสดงผลด้วยฟังก์ชันบนเบราวน์เซอร์อย่างเช่น prompt และ alert และนี่เป็นเนื้อหาในบทนี้

  • การแสดงผลออกทางหน้าจอ
  • การแสดงผลด้วยการแทนที่ใน String
  • การแสดงผลด้วย process.stdout.write
  • การรับค่าผ่านทางคีย์บอร์ด (Node.js)
  • การรับค่าจาก Command line อาร์กิวเมนต์
  • Escape characters

การแสดงผลออกทางหน้าจอ

ฟังก์ชัน console.log เป็นฟังก์ชันสำหรับแสดงข้อมูลทุกประเภทออกทางหน้าจอในภาษา JavaScript เมื่อเรารันของโค้ดภาษา JavaScript บน Node.js การแสดงผลจะผ่าน Command Line (Console) หรือ Terminal ในขณะที่บนเว็บเบราว์เซอร์การแสดงผลจะผ่านเว็บ Console แทน

นี่เป็นตัวอย่างการใช้งานฟังก์ชัน console.log เพื่อแสดงข้อมูลในภาษา JavaScript

output1.js
console.log("JavaScript");
console.log(12);
console.log(true);

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

JavaScript
12
true

ในตัวอย่าง เราได้ใช้ฟังก์ชัน console.log เพื่อแสดง Literal ของข้อความ ตัวเลข และ Boolean ออกทางหน้าจอตามลำดับ

สังเกตว่าค่าที่ถูกแสดงออกมาในแต่ละคำสั่งนั้นจะอยู่คนละบรรทัดกัน นั่นเป็นเพราะว่าฟังก์ชัน console.log จะแสดงการขึ้นบรรทัดใหม่ "\n" หลังจากที่แสดงค่าของพารามิเตอร์ด้วยเสมอ

นอกจากนี้ เรายังสามารถส่งหลายพารามิเตอร์เข้าไปยังฟังก์ชันได้ ยกตัวอย่างเช่น

console.log("JavaScript", "TypeScript");
console.log(1, 2, 3, 4, 5);

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

JavaScript TypeScript
1 2 3 4 5

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

ฟังก์ชัน console.log ไม่เพียงแค่สามารถแสดงข้อมูลพื้นฐานได้เท่านั้น แต่มันยังสามารถใช้แสดงอออบเจ็ค อาเรย์ ฟังก์ชัน หรือข้อมูลทุกประเภทในภาษา JavaScript

output2.js
let user = {
    name: "Metin",
    age: 28
};
function hello() {
    console.log("Hello World!");
}

console.log(user);
console.log([10, 20, 30, 40, 50]);
console.log(hello);

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

{ name: 'Metin', age: 28 }
[ 10, 20, 30, 40, 50 ]
[Function: hello]

ในตัวอย่าง แสดงให้เห็นว่าฟังก์ชัน console.log นั้นจะแสดงค่าต่างๆ ออกมาในรูปแบบ Literal ของข้อมูล ยกเว้นฟังก์ชันที่แสดงในรูปแบบของ String ที่เป็นชื่อของมัน นี่จะทำให้เราสามารถใช้มันเพื่อ Debug ค่าจากออบเจ็คประเภทต่างๆ ได้เป็นอย่างดี

การแสดงผลด้วยการแทนที่ใน String

นอกจากแสดงผลข้อมูล ตัวแปร หรือออบเจ็คแบบปกติแล้ว ฟังก์ชัน console.log ยังสนับสนุนการแสดงผลด้วยการแทนที่ใน String อีกด้วย โดยการกำหนดพารามิเตอร์แรกเป็น Format string สำหรับจัดรูปแบบการแสดงผล ซึ่ง Format string นั้นจะประกอบไปด้วยตัวกำหนดการแสดงผล (Specifier) ที่แสดงดังในตารางต่อไปนี้

ตัวกำหนดการแสดงผล คำอธิบาย
%o หรือ %O แสดงผลออบเจ็คในภาษา JavaScript
%d แสดงผลตัวเลข
%i แสดงผลตัวเลขจำนวนเต็ม
%s แสดงผลข้อความ
%f แสดงผลตัวเลขทศนิยม

ในการใช้งานฟังก์ชัน console.log สำหรับจัดรูปแบบการแสดงผลด้วยการแทนที่ใน String นั้น ถ้าหากพารามิเตอร์แรกเป็น String ที่มี Specifier ที่ปรากฏในตารางด้านบน ฟังก์ชันจะทำงานในรูปแบบของการแทนที่ใน String แทน

นี่เป็นตัวอย่างการแสดงผลด้วยการแทนที่ใน String ในภาษา JavaScript

string_substitution.js
let name = "Metin";
let height = 6.2;
console.log("%s is %f inches height", name, height);
console.log("%d + %d = %d", 3, 5, 3 + 5);

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

Metin is 6.2 inches height
3 + 5 = 8

ในตัวอย่าง การแสดงผลในบรรทัดแรก เนื่องจากพารามิเตอร์แรกนั้นประกอบไปด้วยตัวจัดรูปแบบการแสดงผล %s และ %f นั่นหมายความว่าการทำงานของฟังก์ชันจะเป็นแบบการแทนค่าใน String พารามิเตอร์ตัวต่อไปจะถูกนำไปแทนที่ในตัวกำหนดรูปแบบตามลำดับที่มันปรากฏใน Format string

console.log("%s is %f inches height", name, height);

ดังนั้นค่าของพารามิเตอร์ name จะถูกนำไปแทนที่ %s และถัดมาพารามิเตอร์ height จะถูกนำไปแทนที่ %f ตามลำดับ และแสดงออกมาทางหน้าจอ

console.log("%d + %d = %d", 3, 5, 3 + 5);

ในบรรทัดต่อมา ได้มีการกำหนดตัวกำหนดรูปแบบการแสดง %d สามตัวใน Format string ดังนั้นฟังก์ชันต้องการพารามิเตอร์อีกสามตัวสำหรับการทำงาน เราได้ส่งค่าตัวเลขเข้าไปยังฟังก์ชันโดยตรง

ตัวอย่างต่อมาเป็นการใช้ตัวกำหนดการแสดงผล %o เพื่อแสดงค่าของออบเจ็คพร้อมกับ String

object_output.js
let user = {
    name: "Metin",
    safary: 50000,
    single: true
};
console.log("Inspect object (%o)", user);
console.log("Inspect object (", user, ")");

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

Inspect object ({ name: 'Metin', safary: 50000, single: true })
Inspect object ( { name: 'Metin', safary: 50000, single: true } )

ในตัวอย่าง เป็นการแสดงค่าของออบเจ็ค เนื่องจากเราต้องการแสดงค่าของออบเจ็คภายในวงเล็บ (...) ดังนั้นการแทนที่ String นั้นเป็นวิธีที่ดีที่เราสามารถทำได้

Inspect object ( { name: 'Metin', safary: 50000, single: true } )

และถึงแม้ว่าเราจะสามารถใช้การส่งค่าแบบหลายพารามิเตอร์ในการแสดงผลบรรทัดที่สอง แต่ฟังก์ชัน console.log จะแสดงเว้นวรรคระหว่างแต่ละพารามิเตอร์ด้วยเสมอ ซึ่งแน่นอนว่านี่ไม่ใช่สิ่งที่เราต้องการ

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

type_conversion.js
console.log("%i", 3.14);
console.log("%s", { name: "Metin", age: 28 });
console.log("Date: %s", new Date());
console.log("Timestamp: %d", new Date());

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

3
{ name: 'Metin', age: 28 }
Date: 2020-07-29T13:37:20.942Z
Timestamp: 1596029840942

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

console.log("%i", 3.14);
console.log("%s", { name: "Metin", age: 28 });

เนื่องจากตัวกำหนดการแสดงผล %i ใช้สำหรับแสดงตัวเลขจำนวนเต็ม ดังนั้น 3.14 จะถูกแปลงเป็นจำนวนเต็มก่อนการแสดงผล และบรรทัดต่อมาเป็นการแสดงออบเจ็คในรูปแบบของ String ด้วย %s

console.log("Date: %s", new Date());
console.log("Timestamp: %d", new Date());

ถัดมาเป็นการแสดงออบเจ็คของวันที่ในรูปแบบของ String ค่าของออบเจ็คถูกแปลงเป็น String จากการเรียกใช้งานเมธอด toString บนออบเจ็ค และสำหรับบรรทัดต่อมา ออบเจ็คของวันที่จะถูกแปลงเป็นตัวเลข ทำให้ได้รับค่าของ Timestamp แทน

คุณอาจจะไม่เห็นความแตกต่างอย่างชัดเจนระหว่างการใช้ %s และ %o สำหรับแสดงออบเจ็ค นั่นเป็นเพราะว่าค่าในออบเจ็คนั้นเป็นประเภทข้อมูลพื้นฐาน ซึ่่งมันจะถูกแสดงตาม Literal ของข้อมูลนั้นๆ ยกตัวอย่างเช่น

let user = {
    name: "Metin",
    age: 28
};

console.log("%s", user);  // { name: 'Metin', age: 28 }
console.log("%o", user);  // { name: 'Metin', age: 28 }

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

object_output2.js
let user = {
    name: "Metin",
    age: 28,
    sayHi: function() {
        console.log("Hi");
    },
    birthDate: {
        month: 3,
        year: 1988
    }
};

console.log("%s", user);
console.log("%o", user);

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

{
  name: 'Metin',
  age: 28,
  sayHi: [Function: sayHi],
  birthDate: [Object]
}
{
  name: 'Metin',
  age: 28,
  sayHi: [Function: sayHi] {
    [length]: 0,
    [name]: 'sayHi',
    [arguments]: null,
    [caller]: null,
    [prototype]: sayHi { [constructor]: [Circular] }
  },
  birthDate: { month: 3, year: 1988 }
}

และอย่างที่คุณเห็น %s จะแปลงออบเจ็คเป็น String โดยออบเจ็คย่อยภายในนั้นจะถูกแปลงในรูปแบบที่เรียบง่ายที่สุดเท่าที่จะเป็นไปได้ ในขณะที่ %o จะแสดงโครงสร้างทั้งหมดของออบเจ็ค รวมทั้ง Property ย่อยภายในออบเจ็ค

ฟังก์ชัน console.log นั้นเป็นฟังก์ชันมาตฐานสำหรับแสดงข้อมูลในภาษา JavaScript ที่สามารถใช้ได้ในทุก Host environment อย่างไรก็ตาม เนื้อหาหลังจากนี้จะเป็นการพูดถึงฟังก์ชันสำหรับรับค่าและแสดงผลที่เฉพาะบน Node.js เท่านั้น นั่นหมายความว่ามันอาจจะไม่ทำงานบน Host environment อื่นๆ เช่น เว็บเบราวน์เซอร์

การแสดงผลด้วยฟังก์ชัน process.stdout.write (Node.js)

ฟังก์ชัน console.log ใช้สำหรับแสดงข้อมูลออกทางหน้าจอที่มีการขึ้นบรรทัดใหม่ อย่างไรก็ตาม เมื่อเราเขียน JavaScript บน Node.js เราสามารถใช้ฟังก์ชัน process.stdout.write สำหรับการแสดงผลออกทางหน้าจอได้ ทั้งสองฟังก์ชันนี้ใช้สำหรับแสดงผลออกทางหน้าจอเหมือนกัน แต่สิ่งที่แตกต่างกันมีดังนี้

การทำงาน console.log process.stdout.write
การขึ้นบรรทัดใหม่ มี ไม่มี
ประเภทพารามิเตอร์ ออบเจ็คทุกประเภท String เท่านั้น
จำนวนพารามิเตอร์ หลายตัว หนึ่งตัวเท่านั้น

จากในตารางนั้นเป็นข้อแตกต่างหลักๆ ที่จะมีผลต่อเราในการเลือกใช้งานฟังก์ชัน process.stdout.write สำหรับการแสดงผลแทนฟังก์ชัน console.log ต่อไปมาดูตัวอย่างการแสดงตัวเลขจาก 0 - 4 ออกทางหน้าจอโดยการใช้ฟังก์ชันทั้งสอง

for (let i = 0; i < 5; i++) {
    console.log(i);
}

for (let i = 0; i < 5; i++) {
    process.stdout.write(i.toString());
}

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

0
1
2
3
4
01234

ในตัวอย่าง เราใช้คำสั่ง for loop สำหรับวนสร้างตัวเลขและแสดงออกมาทางหน้าจอ และอย่างที่เรารู้ว่าฟังก์ชัน console.log จะแสดงการขึ้นบรรทัดใหม่ด้วยเสมอ และเราส่งค่าตัวเลขเข้าไปในฟังก์ชันโดยตรง ในขณะที่ลูปที่สองนั้นเราใช้ฟังก์ชัน process.stdout.write สำหรับการแสดงผล

process.stdout.write(i.toString());

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

ต่อมาเป็นตัวอย่างของโปรแกรมที่แสดงความคืบหน้าของการดาวน์โหลดจาก 1 - 100% และถ้าหากค่าของเปอร์เซนต์ยังไม่ถึง 100% เราได้ทำการลบค่าเดิมออกไปโดยการถอย Cursor การแสดงผลกลับไปตำแหน่งแรกของ Console นี่เป็นเหตุผลที่เราไม่ต้องการให้การแสดงผลขึ้นบรรทัดใหม่และใช้ฟังก์ชัน process.stdout.write สำหรับแสดงเปอร์เซนต์ นี่เป็นโค้ดของโปรแกรม

progression.js
let percent = 0;
let buffer = "";

console.log("Initialing system");

let timerId = setInterval(function () {
    process.stdout.write("\b".repeat(buffer.length));

    if (percent >= 100) {
        clearInterval(timerId);
        console.log("100% Completed");
        console.log("The system has been started");
        return;
    }

    buffer = "Loading " + percent + "%";
    process.stdout.write(buffer);
    percent++;
}, 40);

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

Initialing system
100% Completed
The system has been started

คุณจะต้องรันโปรแกรมเพื่อดูการทำงานของมัน

ในตัวอย่างนี้ เป็นการจำลองโปรแกรมที่มีการโหลดข้อมูลก่อนเริ่มทำงาน และโปรแกรมของเราจะแสดงความคืบหน้าว่าโหลดข้อมูลไปเท่าไหร่แล้วเป็นเปอร์เซนต์จาก 1 - 100% โดยค่าของเปอร์เซนต์จะแสดงบนบรรทัดเดิมด้วยฟังก์ชัน process.stdout.write

let percent = 0;
let buffer = "";

ในตอนแรกเราได้ประกาศตัวแปร percent สำหรับเก็บความคืบหน้าของการดาวน์โหลด ตัวแปร buffer สำหรับเก็บค่าของ String ที่ถูกแสดงผลไปก่อนหน้า เพื่อที่ใช้มันในการถอย Cursor กลับตามความยาวของ String ก่อนหน้า ในการแสดงผลครั้งถัดไป

let timerId = setInterval(function () {
    ...
    percent++;
}, 40);

เราได้ใช้ฟังก์ชัน setInterval เพื่อรันโค้ดในฟังก์ชัน Callback ทุกๆ 40 มิลิวินาที เพิื่ออัพเดทความคืบหน้าของการดาวน์โหลดขึ้นทีละ 1% ฟังก์ชันนี้ส่งค่ากลับเป็น timerId ที่เราสามารถใช้ยกเลิก Timer เมื่อการดาวน์โหลดสิ้นสุดที่ 100% ได้

process.stdout.write("\b".repeat(buffer.length));

ทุุกๆ ครั้งก่อนที่จะมีการแสดงค่าเปอร์เซนต์ความคืบหน้าของการดาวน์โหลดปัจจุบัน เราได้ถอย Cursor กลับไปยังตำแหน่งแรกเพื่อให้การแสดงผลเขียนทับในบรรทัดเดิม

if (percent >= 100) {
    clearInterval(timerId);
    process.stdout.write("100% Completed\n");
    console.log("The system has been started");
    return;
}

นี่เป็นเงื่อนไขเมื่อการดาวน์โหลดเสร็จสิ้น เราได้ยกเลิกการทำงานของ timerId ด้วยฟังก์ชัน clearInterval และแสดงข้อความว่าการดาวน์โหลดสำเร็จ และจบการทำงานของฟังก์ชันด้วยคำสั่ง return

buffer = "Loading " + percent + "%";
process.stdout.write(buffer);
percent++;

ในขณะที่การดาวน์โหลดยังไม่ถึง 100% เราได้แสดงความคืบหน้าของการดาวน์โหลดปัจจุบันด้วยฟังก์ชัน process.stdout.write เพื่อไม่ให้ Cursor ขึ้นบรรทัดใหม่ นั่นจะทำให้เราสามารถย้าย Cursor ไปยังตำแหน่งแรกของบรรทัดด้วยตัวอักษรพิเศษ "\b" เพื่อแสดงข้อความบนบรรทัดเดิมได้

หมายเหตุ: ฟังก์ชัน setInterval และ setTimeout นั้นเป็นฟังก์ชันสำหรับการเขียนโปรแกรมเพื่อให้ทำงานแบบ Asynchronous ซึ่งไม่ได้ครอบคลุมในบทเรียนของเรา

การรับค่าผ่านทางคีย์บอร์ด (Node.js)

ในการเขียนโปรแกรมภาษา JavaScript บน Node.js นั้น เราสามารถรับค่าจากทางคีย์บอร์ดเข้ามาใช้ในโปรแกรมของเราได้โดยการใช้งานฟังก์ชันจากโมดูล readline นี่เป็นตัวอย่างที่ง่ายที่สุดในการรับค่าจากทางคีย์บอร์ดผ่าน Command line ในภาษา JavaScript

readline.js
const readline = require('readline');

const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

rl.question("Enter your name: ", function (answer) {
    console.log("Hello " + answer + "!");
    rl.close();
});

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

Enter your name: Mateo
Hello Mateo!

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

const readline = require('readline');

const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

ในตอนแรกเป็นการนำเข้าโมดูล readline ด้วยเมธอด require หลังจากนั้นสร้างออบเจ็ค Interface สำหรับการรับค่าและการแสดงผลที่เชื่อมต่อกับ stdin และ stdout ซึ่งก็คือคีย์บอร์ดและหน้าจอตามลำดับ

rl.question("Enter your name: ", function (answer) {
    console.log("Hello " + answer + "!");
    rl.close();
});

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

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

readline2.js
const readline = require('readline');

const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

rl.question("Enter your name: ", function (answer) {
    console.log("Hello " + answer + "!");

    rl.question("Enter your birth year: ", function (answer) {
        let age = new Date().getFullYear() - Number(answer);
        console.log("You're " + age + " years old");
        rl.close();
    });
});

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

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

เพื่อแก้ปัญหานี้ เราต้องเปลี่ยนการทำงานของการรับค่าให้เป็นแบบ Synchronous โดยการใช้ Promise และคำสั่ง async/await เข้ามาช่วย นี่จะทำให้การรับค่าด้วยเมธอด question ทำงานเป็นแบบ synchronous เหมือนกับภาษาที่สนับสนุนการรับค่าจาก Console เช่น ภาษา C หรือภาษา Java

จากตัวอย่างด้านบนเราได้แปลงโค้ดของการรับค่าให้ทำงานแบบ synchronous นี่เป็นโค้ดของโปรแกรม

synchronous_readline.js
const readline = require('readline');

function prompt(question) {
    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout
    });
    return new Promise(function (resolve, reject) {
        rl.question(question, function (answer) {
            rl.close();
            resolve(answer.trim());
        });
    });
}

(async function () {

    let name = await prompt("Enter your name: ");
    console.log("Hello " + name + "!");

    let year = await prompt("Enter your birth year: ");
    let age = new Date().getFullYear() - Number(year);
    console.log("You're " + age + " years old");

    let a = Number(await prompt("Enter first number: "));
    let b = Number(await prompt("Enter second number: "));
    console.log("%d + %d = %d", a, b, a + b);

})();

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

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

Enter your name: Mateo
Hello Mateo!
Enter your birth year: 1988
You're 32 years old
Enter first number: 5
Enter second number: 8
5 + 8 = 13

ตอนนี้โปรแกรมถามให้เรากรอกค่ากรอกค่าจากทางคีย์บอร์ด 4 ครั้งและการทำงานเป็นไปตามลำดับ

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

function prompt(question) {
    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout
    });
    return new Promise(function (resolve, reject) {
        rl.question(question, function (answer) {
            rl.close();
            resolve(answer);
        });
    });
}

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

rl.question(question, function (answer) {
    rl.close();
    resolve(answer);
});

ภายในฟังก์ชัน Callback ของ Promise เราได้เรียกใช้เมธอด question เพื่อรับค่าจากทางคีย์บอร์ด และเช่นเดิมการทำงานในฟังก์ชันนี้เป็นแบบ Asynchronous ซึ่งไม่สำคัญแล้วในตอนนี้ แต่สิ่งหนึ่งที่เรารู้คือเมื่อการรับค่าเสร็จสิ้น (ปุ่ม Enter ถูกกด) ฟังก์ชัน Callback ของเมธอด question ส่งค่าที่รับเข้ามาผ่านทางพารามิเตอร์ answer

resolve(answer);

ตอนนี้เราได้ค่าที่รับมาจากคีย์บอร์ดแล้ว สิ่งสุดท้ายที่เราต้องทำคือจบการทำงานของ Promise ดังนั้นเราเรียกใช้ฟังก์ชัน resolve โดยส่งค่าในตัวแปร answer เข้าไปในฟังก์ชันเพื่อเป็นผลสำเร็จของ Promise

let name = await prompt("Enter your name: ");

หลังจากนั้นเราเรียกใช้งานเมธอด prompt เพื่อรับค่าตามปกติ ตอนนี้สังเกตว่าจะมีคำสั่ง await ก่อนชื่อของเมธอดด้วย คำสั่งนี้จะทำให้โค้ดบรรทัดนี้รอการทำงานจากฟังก์ชัน prompt จนกว่า Promise จะทำการส่งค่ากลับมาด้วยคำสั่ง resolve(answer) นั่นเอง

// This line instructs promise to return value to `await prompt`
resolve(answer); 
...
// Pause execution and wait for answer
let name = await prompt("Enter your name: ");

หรือกล่าวอีกนัยหนึ่ง await prompt จะหยุดเพื่อรอให้การทำงานในฟังก์ชัน prompt เสร็จก่อนจนกว่า resolve(answer) จะถูกเรียก นั่นทำให้การทำงานในฟังก์ชัน prompt จะส่งผลลัพธ์เป็น answer กลับมาออกมา นั่นทำให้เราสามารถประกาศตัวแปร name มารับค่าได้เลย

(async function () {
    ...
})();

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

การรับค่าจาก Command line อาร์กิวเมนต์

ในการเขียนภาษา JavaScript บน Node.js เราสามารถส่งอาร์กิวเมนต์ผ่าน Command line เพื่อนำไปใช้งานในโปรแกรมได้ เราสามารถเข้าถึงอาร์กิวเมนต์ที่ส่งเข้าไปได้ทางอาเรย์ process.argv ซึ่งเป็นตัวแปรที่ให้มาโดย Node.js

ยกตัวอย่างเช่น เราต้องการส่งค่า Metin และ 5 เข้าไปในโปรแกรมของเรา ดังนั้นในตอนรันโปรแกรม เราสามารถส่งอาร์กิวเมนต์เพิ่มได้หลังจากชื่อของไฟล์ และคั่นแต่ละอาร์กิวเมนต์ด้วยช่องว่าง

node command_line_argv.js Metin 5

และนี่เป็นวิธีที่เรารับเอาค่าอาร์กิวเมนต์ดังกล่าวภายในโปรแกรม

command_line_argv.js
console.log(process.argv);

console.log("Node: " + process.argv[0]);
console.log("File: " + process.argv[1]);
console.log("Param 1: " + process.argv[2]);
console.log("Param 2: " + process.argv[3]);

ในตัวอย่าง เป็นการเข้าถึงค่าที่ส่งผ่าน Command line เข้ามาในตอนรันโปรแกรม เราสามารถเข้าถึงได้จากตัวแปรอาเรย์ process.argv

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

Node: C:\Program Files\nodejs\node.exe
File: C:\JavaScript\command_line_argv.js
Param 1: Metin
Param 2: 5

และอย่างที่คุณเห็น อาร์กิวเมนต์ตัวแรกในอาเรย์นั้นจะเป็นที่อยู่ของไฟล์ Node.js และอาร์กิวเมนต์ตัวที่สองจะเป็นที่อยู่ของไฟล์โปรแกรมที่กำลังทำงานอยู่ในตอนนี้เสมอ

console.log("Param 1: " + process.argv[2]);
console.log("Param 2: " + process.argv[3]);

ดังนั้นเพื่อเข้าถึงค่าอาร์กิวเมนต์ที่เราต้องการจริงๆ เราจะต้องเริ่มอ่านค่าจาก Index ตำแหน่งที่สองของอาเรย์

เนื่องจากแต่ละอาร์กิวเมนต์ที่ส่งเข้าไปผ่าน Command line นั้นจะคั่นด้วยเว้นวรรค ถ้าหากคุณต้องการส่งค่าของ String ที่มีเว้นวรรคด้วย คุณสามารถครอบมันด้วยเครื่องหมาย Double quote (") นั่นจะทำให้ข้อความทั้งหมดภายในถูกรวมเป็นอาร์กิวเมนต์เดียวกัน ยกตัวอย่างเช่น

node string_argv.js 5 "Hello World!"

ในคำสั่งการรันโปรแกรม string_argv.js ด้านบนนั้นจะส่งข้อความ "Hello World!" และเราสามารถรับได้ภายในโปรแกรมได้ดังนี้

string_argv.js
let loop = Number(process.argv[2]);
let message = process.argv[3];

for (let i = 0; i < loop; i++) {
    console.log("%d: %s", i + 1, message);
}

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

1: Hello World!
2: Hello World!
3: Hello World!
4: Hello World!
5: Hello World!

หมายเหตุ: ค่าที่ถูกส่งผ่าน Command line อาร์กิวเมนต์จะเป็น String เสมอ ดังนั้นหากเราต้องการนำมันไปใช้ในรูปแบบอื่น เช่น ตัวเลข เราจะต้องแปลงเป็นตัวเลขด้วยฟังก์ชัน Number ก่อน

Escape characters

เนื้อหาสำหรับอ่านเพิ่มเติมเกี่ยวกับการแสดงผลในภาษา JavaScript คือ Escape characters; Escape character คือตัวอักษรพิเศษที่ไม่สามารถแสดงเป็นตัวอักษรในโค้ดของโปรแกรมได้ อาจเป็นเพราะว่ามันเป็นตัวอักษรที่เป็นส่วนหนึ่งของไวยากรณ์ของภาษาหรืออื่นๆ

ยกตัวอย่างเช่นเพื่อแสดงผลการขึ้นบรรทัดใหม่ภายในข้อความ เราต้องใช้ตัวอักษร "\n" หรือการแสดงแท็บ เราสามารถใช้ตัวอักษร "\t" ซึ่งตัวอักษรเหล่านี้เรียกว่า Escape character นี่เป็นตัวอย่าง

console.log("ID\tNAME\n1\tMetin\n2\tMateo");

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

ID  NAME
1   Metin
2   Mateo

ถ้าหากต้องการ คุณสามารถเรียนรู้เกี่ยวกับมันได้ที่บท Escape characters ในภาษา JavaScript

ฟังก์ชันอื่นๆ ในเนมสเปซ console

นอกจากฟังก์ัชัน console.log ที่เราสามารถใช้แสดงผลออกทางหน้าจอแล้ว ในภาษา JavaScript ยังมีฟังก์ชันอื่นๆ ที่อยู่ในในเนมสเปซ console ด้วยเช่นกัน เช่น ฟังก์ชัน console.error ใช้สำหรับแสดงข้อความที่บอกว่านี่เป็นข้อผิดพลาดที่เกิดขึ้น

นี่เป็นตัวอย่างการใช้งาน

try {
    hello()
} catch(e) {
    console.error(e.message);
}

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

สำหรับฟังก์ชันทั้งหมดคุณสามารถดูได้ที่ Console Web APIs; อย่างไรก็ตาม บางฟังก์ชันอาจจะทำงานแตกต่างกันในแต่ละ Host environment

ในบทนี้ คุณได้เรียนรู้เกี่ยวกับการรับค่าและการแสดงผลในภาษา JavaScript เราได้พูดถึงการใช้งานฟังก์ชันการแสดงผลพื้นฐานอย่าง console.log การจัดรูปแบบการแสดงผล การรับค่าจากทางคีย์บอร์ดและผ่าน Command line อาร์กิวเมนต์ ซึ่งเป็นการรับค่าที่สามารถทำได้บน Node.js เท่านั้น

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