φυβλαςのβλογ
phyblas的博客



javascript เบื้องต้น บทที่ ๔๔: การสร้างฟังก์ชันสำหรับทำงานแบบไม่ประสานเวลาด้วย async และ await
เขียนเมื่อ 2020/05/07 00:46


ต่อจากบทที่แล้วที่ได้เขียนถึงการสร้างออบเจ็กต์ Promise ขึ้นมาใช้จัดการกับการทำงานแบบไม่ประสานเวลา

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

วิธีนี้เพิ่งถูกเพิ่มมาให้ใช้ได้ตั้งแต่ใน ES2017




การสร้างฟังก์ชัน async

async เป็นคำสั่งที่เอาไว้วางหน้าส่วนที่สร้างฟังก์ชัน เพื่อทำให้ฟังก์ชันนั้นเป็นฟังก์ชันที่สร้าง Promise ขึ้นมา แทนที่จะเป็นฟังก์ชันที่ให้ผลทันที

ฟังก์ชันโดยปกติทั่วไปเมื่อเรียกใช้แล้วก็จะทำงานทันทีและจะคืนค่าที่ return ในฟังก์ชันออกมา
function f(){
  return "ลุยไปเลยไม่ต้องรอ !!!";
}

alert(f()); // ได้ ลุยไปเลยไม่ต้องรอ !!!

แต่เมื่อเติม async นำหน้าไปตอนสร้างฟังก์ชัน จะกลายเป็นฟังก์ชันที่คืนออบเจ็กต์ Promise และต้องนำมาใช้กับ .then() อีกทีแล้วจะได้ค่าที่คืนมาในฟังก์ชันนั้นเป็นพารามิเตอร์ของฟังก์ชันใน .then() แล้วจึงเอามาใช้
async function f(){
  return "สัญญาว่าเดี๋ยวจะลุย~~";
}

alert(f()); // ได้ [object Promise]

f().then((x) => {
  alert(x); // ได้ สัญญาว่าเดี๋ยวจะลุย~~
});

ดังนั้นอาจเทียบได้ว่าการเติม async ลงไปเหมือนกับเป็นการสร้างฟังก์ชันที่ return ออบเจ็กต์ Promise มา แบบนี้
function f() {
  return new Promise((resolve) => {
    resolve("สัญญาว่าเดี๋ยวจะลุย~~");
  });
}

ฟังก์ชันที่สร้างจากโดยเติม async นำหน้าแบบนี้เรียกว่าฟังก์ชัน async

ในตัวอย่างนี้เขียนเป็น async function f() {} แบบนี้ แต่จะใช้วิธีการสร้างฟังก์ชันแบบอื่นก็ได้ แค่เติม async นำหน้า

เช่น let f = async function() {}

หรือสร้างฟังก์ชันแบบลูกศรแบบนี้ก็ได้ let f = async () => {}

ปกติแล้วฟังก์ชันในตัว Promise เองจะทำงานทันทีเมื่อ Promise ถูกสร้างขึ้นอยู่แล้ว ดังนั้นฟังก์ชัน async เองเมื่อถูกเรียกใช้ก็จะทำงานทันทีเช่นเดียวกัน
async function tueanphai(x){
  alert(x);
}

tueanphai("ไฟไหม้"); // alert ทำงานทันที

ตัวอย่างนี้ตัด async ออกก็ให้ผลเหมือนเดิม

เพียงแต่ถ้ามีค่าที่ return จะไม่ได้คืนค่ากลับมาให้ทันที ต้องใช้ .then() จึงจะเอาค่าที่ return มาใช้ได้

ดังนั้นกรณีที่เป็นฟังก์ชันที่เรียกใช้เพื่อให้ทำงานตามคำสั่งเฉยๆ ไม่ได้สนค่าคืนกลับด้วย return แบบนี้การเติม async ไปก็ไม่ได้ทำให้เกิดผลแตกต่างไปจากเดิม




ใช้คำสั่ง await แทน then

นอกจาก async แล้ว อีกคำสั่งที่ถูกเพิ่มเข้ามาพร้อมกันและมักถูกใช้ควบคู่กันไปก็คือ await

ในขณะที่ async สามารถแทนการสร้าง Promise โดยตรงได้ await ก็มีไว้แทนการใช้ .then() .catch() .finally()

await ใช้วางไว้ด้านหน้าออบเจ็กต์ Promise เพื่อเป็นการรอให้ฟังก์ชันใน Promise นั้นทำงานเสร็จ แล้วรับผลที่ได้จากฟังก์ชันนั้นมา แล้วทำงานต่อจากในฟังก์ชันนั้น

แต่คำสั่ง await มีข้อจำกัดคือจะใช้ได้แค่เฉพาะในฟังก์ชัน async เท่านั้น

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

แต่คำสั่งทั่วไปที่ทำงานแบบประสานเวลาจะรันในเธรดหลักเสมอ งานในเธรดหลักจะไม่สามารถมีการหยุดรอใดๆได้

ในขณะที่ฟังก์ชัน async จะถูกรันแบบไม่ประสานเวลาโดยไปรันในเธรดลูก ไม่ใช่รันในเธรดหลักโดยตรง ดังนั้น await จึงใช้ได้เฉพาะในฟังก์ชัน async

ตัวอย่างการใช้ เช่น
let p = new Promise((resolve) => {
  setTimeout(() => {
    resolve("หนัง");
  },500);
});

async function chainang() {
  let nang = await p;
  return "*~" + nang + "~%";
}

chainang().then((x) => {
  alert(x); // ได้ *~หนัง~%
});

ในที่นี้ภายในฟังก์ชัน chainang ในที่นี้ใช้ await แต่ถ้าเขียนโดยไม่ใช้ await แค่ใช้ .then() เหมือนอย่างแบบเก่าก็อาจเขียนได้แบบนี้
async function chainang() {
  return p.then((nang) => {
    return "*~" + nang + "~%";
  });
}

หรือถ้าไม่ใช้แม้แต่ async เลยก็จะกลายเป็นแบบนี้
function chainang() {
  return new Promise((resolve) => {
    p.then((nang) => {
      resolve("*~" + nang + "~%");
    });
  });
}

ดังนั้นการใช้ async และ await จึงทำให้การเขียนดูเรียบง่ายขึ้นมาก

อย่างไรก็ตาม await ใช้ได้เฉพาะในฟังก์ชัน async เท่านั้น ดังนั้น .then() ในส่วนนี้
chainang().then((x) => {
  alert(x);
});

จึงไม่อาจแทนด้วย await ได้ เพราะอยู่นอกฟังก์ชัน async ยังคงจำเป็นต้องใช้ .then()

อย่างไรก็ตาม หากต้องการใช้ await จริงๆก็อาจทำได้ด้วยวิธีการที่อ้อมค้อมสักหน่อย เช่นการสร้างฟังก์ชัน async มาแล้วเรียกใช้ทันที เช่น
(async () => {
  let x = await chainang();
  alert(x);
})();

แบบนี้อาจดูซับซ้อนแปลกตาสักหน่อย แต่เขาก็ใช้กันแบบนี้จริงๆ




ฟังก์ชัน async กับข้อผิดพลาด

ฟังก์ชัน async นั้นหากเกิดข้อผิดพลาดขึ้นจะเหมือนเป็นการเรียก reject ใน Promise นั่นคือ จะทำให้ .catch() ทำงาน

เช่น
async function f() {
  t += 1;
}

f().catch((e) => {
  alert(e); // ขึ้นกรอบข้อความว่า ReferenceError: t is not defined
})

ข้อผิดพลาดในฟังก์ชัน async นั้นไม่สามารถรับด้วยโครงสร้าง try catch ได้
async function f() {
  v += 1;
}

try {
  f() // ขึ้นข้อผิดพลาดขึ้นมาตามปกติเป็น ReferenceError: t is not defined
}
catch (e) { // ตรงนี้ไม่ทำงาน
  alert(e);
}

ดังนั้นหากต้องการจัดการกับข้อผิดพลาดของฟังก์ชัน async ให้ใช้ .catch() แทนที่จะใช้ try catch อาจสะดวกกว่า

หรือหากต้องการใช้ try catch แทนก็สามารถทำได้โดยใช้ร่วมกับ await




การใช้ await ในกรณีที่ต้องรับมือข้อผิดพลาด

ปกติถ้าใช้โครงสร้าง .then() และ .catch() เวลามีข้อผิดพลาดจะไปใส่ใน .catch()

แต่หากใช้ await แทน then แล้วจะไม่สามารถใช้ .catch() ได้ แบบนี้จะจัดการกับข้อผิดพลาดได้อย่างไร

คำตอบคือ ใช้ try catch เหมือนกับกรณีประสานเวลาทั่วๆไปได้เลย

โดยหาก Promise ที่ await รับมานั้นมีข้อผิดพลาด ข้อผิดพลาดก็จะสะท้อนออกมาตรงนั้น ซึ่งตรงนี้สามารถใช้ try catch ดักจับเพื่อรับมือได้

ตัวอย่างการใช้
async function f() {
  t += 1;
}

(async () => {
  try {
    await f();
  }
  catch (e) {
    alert(e); // ขึ้นกรอบข้อความว่า ReferenceError: t is not defined
  }
})();

ตรงนี้หากตัด await ออก ความผิดพลาดจะไม่ได้ถูก try catch จับไว้ และจะแสดงข้อผิดพลาดออกมาตามปกติ ไม่ได้เข้า catch

จะเห็นว่า await นั้นสามารถใช้แทนได้ทั้ง .then() และ .catch() ไปในขณะเดียวกัน และยังทำให้ดูเข้าใจง่ายกว่าเดิมด้วย




นรกเรียกกลับ

ตัวอย่างข้อดีของการใช้ await ซึ่งมักถูกยกมาพูดถึงบ่อยๆ นั่นคือช่วยลดความซับซ้อนลงยิ่งกว่าการใช้ .then ทำให้โค้ดดูเข้าใจง่ายขึ้น เห็นลำดับการทำงานได้ดีขึ้น

ปกติถ้าใช้ .then() จะต้องจัดการอะไรต่างๆในฟังก์ชันที่ยัดใส่ไว้ใน .then() และถ้าต้องการเอาผลที่ได้จากในนั้นไปใช้ ก็อาจต้องวาง .then() ซ้อนเข้าไปด้านในอีกชั้น หรือไม่ก็วาง .then() ต่อกันไปจากตัวเก่า

เช่นมี ๓​ ฟังก์ชันที่ต้องการให้ทำงานต่อๆกันไปแบบนี้ กรณีที่เขียน .then() ซ้อนเข้าไปเรื่อยๆ อาจเขียนแบบนี้
function luakphak(phak) { // ลวกผักที่เตรียมไว้
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("~" + phak + "~");
    }, 500);
  });
}

function hanphak(phak) { // หั่นผักที่ลวกแล้ว
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("x" + phak + "x");
    }, 1000);
  });
}

function thotphak(phak) { // ทอดผักที่หั่นแล้ว
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("*" + phak + "*");
    }, 1500);
  });
}

// เริ่มทำได้
let kalam = "กะหล่ำปลี";
luakphak(kalam).then((x) => {
  alert(x); // ได้ ~กะหล่ำปลี~
  hanphak(x).then((y) => {
    alert(y); // ได้ x~กะหล่ำปลี~x
    thotphak(y).then((z) => {
      alert(z); // ได้ *x~กะหล่ำปลี~x*
    });
  });
});

การซ้อนกันแบบนี้เป็นวิธีหนึ่งที่อาจทำได้ แต่ดูแล้วซับซ้อน ยิ่งซ้อนยิ่งลึกลงไป ลักษณะแบบนี้ถูกผู้คนเรียกกันว่าเป็น "นรกเรียกกลับ" (callback hell, 回调地狱)

โค้ดอาจซ้อนกันหลายชั้นลึกจนน่ากลัว ดูแล้วเหมือนริวปล่อยฮาโดเคน อะบู๊เก็ต
(ที่มาของภาพ)

จริงๆแล้วมีวิธีเขียนที่อาจจะดูดีกว่านั้น คือใช้การ return เพื่อส่งข้อมูลให้ .then() ตัวต่อๆไป แบบนี้ก็จะวาง .then() ต่อๆกันไปแทนที่จะซ้อนกัน แบบนี้จะดูง่ายกว่า
let kalam = "กะหล่ำปลี";
luakphak(kalam).then((x) => {
  alert(x); // ได้ ~กะหล่ำปลี~
  return hanphak(x);
}).then((y) => {
  alert(y); // ได้ x~กะหล่ำปลี~x
  return thotphak(y)
}).then((z) => {
  alert(z); // ได้ *x~กะหล่ำปลี~x*
});

แต่เขียนแบบนี้ก็อาจจะยังดูยุ่งๆอยู่ แต่ตั้งแต่ใน ES2017 สามารถใช้ async await ได้ ซึ่งทำให้การเขียนดูแล้วง่ายขึ้นไปอีก

เมื่อใช้ async await แทน then แล้วก็จะเขียนได้แบบนี้
let kalam = "กะหล่ำปลี";
(async () => {
  let x = await luakphak(kalam);
  alert(x); // ได้ ~กะหล่ำปลี~
  let y = await hanphak(x);
  alert(y); // ได้ x~กะหล่ำปลี~x
  let z = await thotphak(y);
  alert(z); // ได้ *x~กะหล่ำปลี~x*
})();

จะเห็นว่าเมื่อใช้ await ก็จะสามารถดึงข้อมูลที่คืนจาก Promise มาใช้ได้เลย ไม่ต้องมาเรียกใช้ใน .then()

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

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

เพียงแต่ต้องไม่ลืมว่า await ใช้ได้เฉพาะในฟังก์ชัน async ดังนั้นจึงต้องสร้างฟังก์ชัน async มาคลุมส่วนที่จะใช้ await และคำสั่งที่จะรอ await ทำเสร็จก่อนก็จะเป็นคำสั่งในส่วนนี้เท่านั้น

เช่นถ้าแก้เป็นแบบนี้
let kalam = "กะหล่ำปลี";
let s = [];

(async () => {
  s.push("ก. ใส่ " + kalam);
  let x = await luakphak(kalam);
  s.push("ค. ได้ " + x);
  let y = await hanphak(x);
  s.push("ง. ได้ " + y);
  let z = await thotphak(y);
  s.push("จ. ได้ " + z);

  alert(s.join("\n")); // ให้ขึ้นหน้าต่างแสดงผลออกมา
})();

s.push("ข. เริ่ม ");

ผลที่ได้จะออกมาตามลำดับแบบนี้
ก. ใส่ กะหล่ำปลี
ข. เริ่ม 
ค. ได้ ~กะหล่ำปลี~
ง. ได้ x~กะหล่ำปลี~x
จ. ได้ *x~กะหล่ำปลี~x*

ภายในฟังก์ชัน async นั้นส่วน ก. อยู่ก่อน await จึงเริ่มทำงานไปตั้งแต่แรก ในขณะที่ส่วน ค. ง. จ. ล้วนอยู่ต่อจาก await จึงต้องรอทำทีหลัง ซึ่งจะช้ากว่าส่วน ข. ที่เขียนท้ายสุดด้านนอกฟังก์ชัน async

ข้อดีอีกอย่างที่เห็นได้ชัดอย่างหนึ่งจากการใช้ await ก็คือ สามารถเขียนในรูปของการวนทำซ้ำด้วย for หรือ while ได้ เช่น
function f(x) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("~*" + x + "*~");
    }, 200); // รอ ๐.๒ วินาที
  });
}

(async () => {
  let t0 = new Date; // จับเวลาเริ่มต้น
  let log = []; // ตัวบันทึกแต่ละขั้นตอน
  let x = "ป";
  let i = 0; // รอบที่
  while(i<6){
    x = await f(x); // เรียกฟังก์ชัน async แล้วใช้ await รับค่า
    log.push(x + " " + (new Date -t0)); // เอาผลที่ได้ใส่ตัวบันทึก
    i++;
  }
  alert(log.join("\n")); // แสดงสิ่งที่บันทึกเอาไว้ทั้งหมด
})();

ผลที่ได้ก็จะแสดงผลการวนแต่ละรอบห่างกันประมาณ ๐.๒ วินาที โดยแต่ละรอบก็มี ~* นำหน้า และ *~ ต่อท้ายเพิ่มเข้าไป ตัวเลขเวลาเป็นมิลลิวินาทีแสดงอยู่ท้ายสุด
~*ป*~ 204
~*~*ป*~*~ 405
~*~*~*ป*~*~*~ 605
~*~*~*~*ป*~*~*~*~ 808
~*~*~*~*~*ป*~*~*~*~*~ 1009
~*~*~*~*~*~*ป*~*~*~*~*~*~ 1213

ในที่นี้ถ้าใช้เป็น .then() ซ้อนกันเป็นนรกแบบเดิมอาจต้องเขียนซ้อนกัน ๖ ชั้น หรือไม่ก็ต้องสร้างฟังก์ชันเวียนเกิด ซึ่งไม่ว่าจะยังไงก็ดูแล้วซับซ้อนกว่า




fetch

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

fetch เป็นฟังก์ชันสำหรับโหลดข้อมูลจากเว็บ วิธีการใช้งานก็ง่ายๆโดยเขียนแบบนี้
fetch(url ของเว็บที่ต้องการดึงข้อมูล)

เพียงแต่ว่าเว็บโดยทั่วไปจะมีการป้องกันการใช้ฟังก์ชันจาวาสคริปต์ดึงข้อมูลโดยตรงแบบนี้ จะติดปัญหาเรื่องการแบ่งปันทรัพยากรณ์ข้ามแหล่งที่มา (Cross-origin resource sharing, CORS)

ในที่นี้จะยกตัวอย่างโดยใช้เว็บ https://qiita.com/api/v2/docs ซึ่งเป็นเว็บจำพวก API ซึ่งอนุญาตให้ใช้จาวาสคริปต์ดึงข้อมูลได้ ดังนั้นจึงใช้ fetch ได้ (เพียงแต่จะมีการจำกัดจำนวนครั้ง ถ้าทำซ้ำบ่อยหลายครั้งก็อาจถูกบล็อกให้ต้องรออีกสักพักจึงจะทำใหม่ได้)

fetch ไม่ใช่ฟังก์ชันมาตรฐานใน ES6 แต่ก็เป็นฟังก์ชันที่สามารถใช้ได้ในจาวาสคริปต์ในเบราเซอร์ทั่วไป ถ้ารันในเบราเซอร์ก็ควรจะใช้ฟังก์ชันนี้ได้ตามปกติ

ฟังก์ชัน fetch เมื่อใช้แล้วจะให้ออบเจ็กต์ Promise มา สามารถนำมาใช้ .then() หรือ await ต่อได้

ในที่นี้ขอแสดงตัวอย่างการใช้กับ await
(async () => {
  let url = "https://qiita.com/api/v2/docs";
  let r = await fetch(url); // เรียกใช้ฟังก์ชัน fetch แล้วได้ออบเจ็กต์ Response มา
  alert(r.ok); // ได้ true
  let text = await r.text(); // เรียกใช้เมธอด .text ที่ตัวออบเจ็กต์ Response แล้วได้เนื้อหาข้างในมา
  alert(text); // แสดงเนื้อหาในเว็บ
})();

ผลที่ได้ก็จะได้กรอบข้อความซึ่งแสดงโค้ด html ของเว็บนั้น

ในที่นี้เริ่มจากเรียกฟังก์ชัน fetch โดยใส่ url ของเว็บที่ต้องการเข้าไป จากนั้นจะได้ Promise คืนมา พอใช้ await ก็จะได้ออบเจ็กต์ Response มา

ออบเจ็กต์ Response นี้มีพรอเพอร์ตี .ok ซึ่งจะบอกสถานะของเว็บนั้น ถ้าใช้ได้ก็จะได้ true

หากต้องการดูเนื้อหาข้างในให้ใช้เมธอด .text() ซึ่งผลที่ได้ก็จะเป็นออบเจ็กต์ Promise อีกเช่นกัน ก็ต้องใช้ await จึงจะได้ข้อความที่เป็นเนื้อหาข้างในเว็บมา ในที่นี้คือโค้ด html




เทียบกับในภาษาไพธอน



ในภาษาไพธอนเองก็มีไวยากรณ์ async await แบบนี้เช่นกัน ใช้กับมอดูลชื่อ asyncio และวิธีการใช้ก็คล้ายกันมาก จึงน่าสังเกตและเทียบกันดู

วิธีการทำงานแบบไม่ประสานเวลาโดยใช้ async await ในไพธอนได้เขียนไว้ในบทความนี้ https://phyblas.hinaboshi.com/20200315

async ในไพธอนเองก็เอาไว้สร้างฟังก์ชัน async เหมือนกัน โดยค่าที่คืนกลับของในไพธอนจะได้ออบเจ็กต์โครูทีน (coroutine) และสร้างภารกิจ (task) ซึ่งก็เทียบเท่ากับออบเจ็กต์ Promise ในจาวาสคริปต์

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

มองโดยรวมๆแล้วก็จะเห็นว่ามีอะไรคล้ายคลึงกันมาก แม้ว่าจะมีความแตกต่างกันไปพอสมควรในรายละเอียด แต่หากเข้าใจหลักการในภาษาหนึ่งแล้วก็สามารถข้ามเรียนรู้อีกภาษาได้ไม่ยาก






-----------------------------------------

囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧

ดูสถิติของหน้านี้

หมวดหมู่

-- คอมพิวเตอร์ >> เขียนโปรแกรม >> javascript

ไม่อนุญาตให้นำเนื้อหาของบทความไปลงที่อื่นโดยไม่ได้ขออนุญาตโดยเด็ดขาด หากต้องการนำบางส่วนไปลงสามารถทำได้โดยต้องไม่ใช่การก๊อปแปะแต่ให้เปลี่ยนคำพูดเป็นของตัวเอง หรือไม่ก็เขียนในลักษณะการยกข้อความอ้างอิง และไม่ว่ากรณีไหนก็ตาม ต้องให้เครดิตพร้อมใส่ลิงก์ของทุกบทความที่มีการใช้เนื้อหาเสมอ

目录

从日本来的名言
模块
-- numpy
-- matplotlib

-- pandas
-- opencv
-- pytorch
机器学习
-- 神经网络
maya
javascript
概率论
与日本相关的日记
与中国相关的日记
-- 与北京相关的日记
-- 与香港相关的日记
-- 与澳门相关的日记
与台湾相关的日记
与北欧相关的日记
与其他国家相关的日记
qiita
其他日志

按类别分日志



ติดตามอัปเดตของบล็อกได้ที่แฟนเพจ

  查看日志

  推荐日志

ภาษาจีนแบ่งเป็นสำเนียงอะไรบ้าง มีความแตกต่างกันมากแค่ไหน
ทำความเข้าใจระบอบประชาธิปไตยจากประวัติศาสตร์ความเป็นมา
เรียนรู้วิธีการใช้ regular expression (regex)
หลักการเขียนทับศัพท์ภาษาจีนกวางตุ้ง
การใช้ unix shell เบื้องต้น ใน linux และ mac
หลักการเขียนทับศัพท์ภาษาจีนกลาง
g ในภาษาญี่ปุ่นออกเสียง "ก" หรือ "ง" กันแน่
ทำความรู้จักกับปัญญาประดิษฐ์และการเรียนรู้ของเครื่อง
ค้นพบระบบดาวเคราะห์ ๘ ดวง เบื้องหลังความสำเร็จคือปัญญาประดิษฐ์ (AI)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
บันทึกการเที่ยวสวีเดน 1-12 พ.ค. 2014
แนะนำองค์การวิจัยและพัฒนาการสำรวจอวกาศญี่ปุ่น (JAXA)
เล่าประสบการณ์ค่ายอบรมวิชาการทางดาราศาสตร์โดยโซวเคนได 10 - 16 พ.ย. 2013
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
บันทึกการเที่ยวญี่ปุ่นครั้งแรกในชีวิต - ทุกอย่างเริ่มต้นที่สนามบินนานาชาติคันไซ
หลักการเขียนทับศัพท์ภาษาญี่ปุ่น
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ
ทำไมถึงอยากมาเรียนต่อนอก
เหตุผลอะไรที่ต้องใช้ภาษาวิบัติ?