DEV Community

Cover image for JavaScript EP7: Promises & Async/Await⏳🪄
phatsss
phatsss

Posted on

JavaScript EP7: Promises & Async/Await⏳🪄

ເຄີຍຮັບມືກັບ JS code ທີ່ແບບວ່າ...ມັນບໍ່ໄດ້ເຮັດວຽກຕາມທີ່ເຮົາຄາດຫວັງໄວ້ບໍ່? ອາດຈະເບິ່ງຄືກັບວ່າ function ຕ່າງໆຖືກ execute ແບບສຸ່ມ-ໝາຍຄວາມວ່າເຮົາບໍ່ສາມາດຄາດເດົາເວລາທີ່ໃຊ້ໃນການ execute ໄດ້ເລີຍ ຫຼື ເກີດເຫດການທີ່ວ່າການ execute ລ່າຊ້າ, ມື້ນີ້ເຮົາຈະມາເບິ່ງ feature ສຸດຄັກທີ່ຊື່ວ່າ Promises ຂອງ JavaScript ນຳກັນ🧙🏻‍♂️.

ຖ້າໃຜຍັງບໍ່ໄດ້ອ່ານ EP1 ທີ່ໄດ້ອະທິບາຍເຖິງການເຮັດວຽກຂອງ Event Loop, ແນະນຳໃຫ້ກັບໄປອ່ານກ່ອນ, ເນື່ອງຈາກມັນເປັນເນື້ອຫາທີ່ກ່ຽວຂ້ອງກັນ.
JavaScript EP1: Event Loop♻️

ເມື່ອເຮົາຂຽນ JS ສິ່ງໜຶ່ງທີ່ເຮົາມັກຈະຕ້ອງໄດ້ພົບຢູ່ຕະຫຼອດກໍ່ຄືການຈັດການ tasks ທີ່ມີການເຮັດວຽກກັບ tasks ອື່ນໆ, ສົມມຸດວ່າເຮົາຕ້ອງການທີ່ຈະ get image, compress, ໃສ່ filter ແລະ ບັນທຶກຮູບນັ້ນໆຈະມີຂັ້ນຕອນການເຮັດວຽກດັ່ງນີ້:

  • ສິ່ງທຳອິດທີ່ເຮົາຈະເຮັດກໍ່ຄືການດຶງເອົາຮູບທີ່ເຮົາຕ້ອງການແກ້ໄຂ(get image), ເຮົາມີ getImage function ທີ່ຈະເຮັດວຽກໃນສ່ວນນີ້.
  • ຫຼັງຈາກໄດ້ຮູບມາແລ້ວຕໍ່ມາຈະເປັນການ resize image ຈະເປັນສ່ວນຂອງ compressImage function ຮັບໄມ້ຕໍ່.
  • ເມື່ອ resize image ສຳເລັດເຮົາຕ້ອງການໃສ່ filter ເຊິ່ງເຮົາມີ applyFilter function ຮັບໜ້າທີ່ສ່ວນນີ້ໄປ.
  • ສຸດທ້າຍແມ່ນຈະເປັນການບັນທຶກຮູບພາບໂດຍໃຊ້ saveImage function ຖ້າຫາກບໍ່ມີຫຍັງຜິດພາດກໍ່ຈະທຳການແຈ້ງໃຫ້ user ຮູ້ວ່າໄດ້ທຳການບັນທຶກຮູບພາບສຳເລັດ.

ສຸດທ້າຍເຮົາຈະໄດ້ code ປະມານນີ້:
getImage function
ສັງເກດເຫັນຫຍັງບາງຢ່າງບໍ່? ຮູ້ສຶກວ່າໃນ code ຂອງເຮົາມີການໃຊ້ nested callback functions ຫຼາຍເກີນໄປເຊິ່ງມັນພັດຂຶ້ນກັບ callback ກ່ອນໜ້າອີກ. callback ໃນລັກສະນະນີ້ມັກຖືກເອີ້ນວ່າ callback hell🤯 ເນື່ອງຈາກມັນມີ callback function ຊ້ອນກັນຫຼາຍເກີນໄປຈຶ່ງເຮັດໃຫ້ code ອ່ານຍາກຂຶ້ນໄປອີກ.

ໂຊກດີທີ່ເຮົາມີສິ່ງທີ່ເອີ້ນວ່າ promises ທີ່ຈະເຂົ້າມາຊ່ວຍເຮົາຈັດການກັບບັນຫາແບບນີ້, ເຮົາໄປທຳຄວາມຮູ້ຈັກກັບ promises ນຳກັນແບບເລິກໆກັນເລີຍ🫡.


Promise Syntax

ຖ້າໃຜເຄີຍໄດ້ອ່ານບົດຄວາມ ຫຼື ເອກະສານໃດໆກໍ່ຕາມທີ່ອະທິບາຍກ່ຽວກັບ Promises ກໍ່ອາດຈະຄຸ້ນຕາກັບນິຍາມ ຫຼື ຄຳອະທິບາຍ Promises ປະມານວ່າ:

"A promise is a placeholder for a value that can either resolve or reject at some time in the future"

ຖ້າຈະເບິ່ງຕາມນິຍາມ ໂດຍສ່ວນຕົວຜູ້ຂຽນເອງກໍ່ຍັງຮູ້ສຶກວ່າມັນຍັງຄຸມເຄືອ ແລະ ບໍ່ຊັດເຈນໃນບາງຈຸດຢູ່, ເພື່ອໃຫ້ເຮົາເຂົ້າໃຈຫຼາຍຂຶ້ນ ເຮົາມາເບິ່ງລັກສະນະການເຮັດວຽກຂອງມັນແທ້ໆເລີຍດີກວ່າ.
ເຮົາສາມາດສ້າງ promise ໂດຍໃຊ້ Promise constructor ທີ່ມີການຮັບ callback function ດັ່ງນີ້:
promise construtor
ເຫັນຫຍັງບໍ່ວ່າມີຫຍັງທີ່ຖືກ return ອອກມາ?
Promise ເປັນ object ໜຶ່ງທີ່ທາງໃນປະກອບມີ status([[PromiseStatus]]) ແລະ value([[PromiseValue]]). ໃນຕົວຢ່າງດດ້ານເທິງເຮົາຈະເຫັນວ່າ value ຂອງ [[PromiseStatus]] ແມ່ນ "pending" ແລະ value ຂອງ promise ແມ່ນ undefined.
ແຕ່ບໍ່ຕ້ອງຫ່ວງ, ເຮົາບໍ່ໄດ້ເຮັດຫຍັງກັບ object ນີ້ດອກ. ໃນຄວາມເປັນຈິງເຮົາບໍ່ສາມາດ access [[PromiseStatus]] ແລະ [[PromiseValue]] properties ໄດ້, ແຕ່ value ຂອງທັງ 2 properties ນີ້ແມ່ນສຳຄັນຫຼາຍເມື່ອເຮົາໃຊ້ promise.


value ຂອງ PromiseStatus ຫຼື state ສາມາດເປັນ 1 ໃນ 3 values ດັ່ງນີ້:

  • fulfilled: promise ໄດ້ທຳການ resolved ໝາຍຄວາມວ່າທຸກຢ່າງເຮັດວຽກໄດ້ສຳເລັດ, ບໍ່ມີ error ໃດໆເກີດຂຶ້ນໃນ promise.
  • rejected: promise ໄດ້ທຳການ rejected ໝາຍຄວາມວ່າມີບາງຢ່າງຜິດພາດ ຫຼື ອາດຈະເກີດ error ຫຍັງບາງຢ່າງໃນ promise.
  • pending: promise ຍັງບໍ່ມີການ resolved ແລະ ໃນຂະນະດຽວກັນກໍ່ຍັງບໍ່ rejected ເຊັ່ນກັນ, ເຊິ່ງມັນຍັງ pending ຢູ່.

ແລ້ວຕອນໃດທີ່ promise status ຈະເປັນ "pending", "fulfilled" ຫຼື "rejected"? ແລະ ສະຖານະຂອງ promise ມັນສຳຄັນຈັ່ງໃດ?

ໃນຕົວຢ່າງດ້ານເທິງເຮົາໄດ້ທຳການ pass callback function () => {} ເຂົ້າໄປໃນ promise constructor. ແຕ່ໃນ callback function ຈະມີການຮັບອີກ 2 arguments, ເຊິ່ງ value ຂອງ argument ທຳອິດເອີ້ນວ່າ resolve ຫຼື res ເປັນ method ທີ່ຈະຖືກເອີ້ນເມື່ອ Promise ທຳການ resolve ແລະ
argument ທີ 2 ເອີ້ນວ່າ reject ແລະ rej ເປັນ method ທີ່ຈະຖືກເອີ້ນຕອນທີ່ Promise ທຳການ reject ຫຼື ມີບາງຢາດຜິດພາດ.
callback arguments
ມາເບິ່ງກັນວ່າເມື່ອເຮົາເອີ້ນໃຊ້ resolve ຫຼື reject method ຈະໄດ້ຜົນລັບແນວໃດ:
call resolve and reject method
ໃນທີ່ສຸດເຮົາກໍ່ຮູ້ວິທີທີ່ເຮັດໃຫ້ promise status ບໍ່ເກີດສະຖານະ "pending" ແລະ undefined value. status ຂອງ promise ຈະເປັນ "fulfilled" ຖ້າເຮົາເອີ້ນໃຊ້ resolve method ແລະ status ຈະເປັນ "rejected" ຖ້າເຮົາເອີ້ນໃຊ້ rejected method.
ລອງມາເບິ່ງໃນສ່ວນຂອງ value ຂອງ promise ກັນຕື່ມ, ເຊິ່ງ value ຂອງ [[PromiseValue]] ຈະເປັນ value ທີ່ເຮົາ pass ເຂົ້າໄປໃນ resolved ຫຼື rejected method ໃນຮູບແບບຂອງ argument ຂອງມັນ.


ມາຮອດຕອນນີ້ເຮົາພໍຈະຮູ້ວິທີຈັດການກັບ Promise object ໃນລະດັບໜຶ່ງແລ້ວ, ຄຳຖາມຕໍ່ມາກໍ່ຄື ມັນໃຊ້ໄວ້ເພື່ອຫຍັງ?🧐.

ໃນຕົວຢ່າງແລກໆແມ່ນເຮົາໄດ້ເຫັນຕົວຢ່າງທີ່ເຮົາໄດ້ທຳການ get image, compress, ໃສ່ filter ແລະ ບັນທຶກຮູບພາບກັນໄປແລ້ວ, ສຸດທ້າຍເຮົາກໍ່ໄດ້ code ທີ່ມີ nasted callback function ແບບເບິ້ມໆມາຊຸດໜຶ່ງ.

Promise ກໍ່ສາມາດນຳມາແກ້ບັນຫາທີ່ວ່າມາໄດ້ເຊັ່ນກັນ, ແຕ່ກ່ອນອື່ນໝົດເຮົາຕ້ອງໄດ້ທຳການຂຽນ code ໃໝ່, ເຊິ່ງເຮົາຈະໃຫ້ແຕ່ລະ function ທຳການ return Promise ແທນ.
ຖ້າຫາກໂຫຼດຮູບສຳເລັດ ແລະ ທຸກຢ່າງເຮັດວຽກໂດຍທີ່ບໍ່ມີ error ໃດໆ, ເຮົາຈະທຳການ resolve promise ດ້ວຍໄຟລ໌ທີ່ຖືກໂຫຼດມາແລ້ວ ໃນທີ່ນີ້ຈະເປັນ image ແລະ ຖ້າຫາກເກີດ error ໃນຂະນະທີ່ທຳການໂຫຼດໄຟລ໌ ເຮົາຈະທຳການ reject promise ດ້ວຍການໂຍນ error ອອກມາແທນ.
getImage return promise
ເມື່ອເຮົາລອງສັ່ງ run code ດັ່ງກ່າວຈະໄດ້ຜົນລັບດັ່ງນີ້:
result of getImage return promise
ຈະເຫັນວ່າ Promise ໄດ້ທຳການ return value ຂອງ parsed data.

ແລ້ວແນວໃດຕໍ່? ເຮົາຈະບໍ່ສົນໃຈ promise object ທັງໝົດທີ່ວ່າມາ, ເຮົາສົນໃຈພຽງ value ຂອງ data ທີ່ຖືກໂຍນອອກມາເທົ່ານັ້ນ, ມັນຈະມີ built-in methods 3 ໂຕທີ່ເຮັດໃຫ້ເຮົາສາມາດດຶງ promise value ອອກມາໃຊ້ໄດ້.

  • .then(): ຖືກເອີ້ນເມື່ອ promise ຖືກ resolved.
  • .catch(): ຖືກເອີ້ນເມື່ອ promise ຖືກ rejected.
  • .finally(): ຈະຖືກເອີ້ນທຸກຄັ້ງບໍ່ວ່າ promise ຈະ resolved ຫຼື rejected ກໍ່ຕາມ. built in method *{ ໃນຮູບດ້ານເທິງໜ້າຈະໃສ່ມາຜິດ

.then method ຈະຮັບ value ທີ່ຖືກ pass ມາໃນ resolve method.
then method
.catch method ຈະຮັບ value ທີ່ຖືກ pass ມາໃນ rejected method.
catch method

ໃນທີ່ສຸດເຮົາກໍ່ໄດ້ value ທີ່ຖືກ resolve ໂດຍ promise ໂດຍທີ່ບໍ່ໄດ້ຢູ່ໃນຮູບແບບຂອງ promise object, ເຊິ່ງເຮົາສາມາດນຳ value ດັ່ງກ່າວໄປເຮັດຫຍັງກໍ່ໄດ້.


ເມື່ອເຮົາຮູ້ແລ້ວວ່າ promise ຈະທຳການ resolve ຫຼື reject ທຸກໆຄັ້ງ, ດັ່ງນັ້ນເຮົາກໍ່ສາມາດຂຽນ Promise.resolve ຫຼື Promise.reject ດ້ວຍການ pass value ທີ່ເຮົາຕ້ອງການ resolve ຫຼື reject ເຂົ້າໄປໃຫ້ promise ໄດ້ດັ່ງນີ້.
Promise.resolve and Promise.reject
ໃນຕົວຢ່າງຕໍ່ໄປກໍ່ຈະໄດ້ໃຊ້ syntax ແບບນີ້ເປັນຫຼັກ.


ຢູ່ໃນຕົວຢ່າງຂອງ getImage ເຮົາມີ nested callback function ຫຼາຍອັນ, ເຊິ່ງເຮົາກໍ່ສາມາດໃຊ້ .then ໃນການຈັດການກັບ code ສ່ວນນີ້🤩.
result ຂອງ .then ເອງກໍ່ແມ່ນ promise value ເຊັ່ນກັນ-ໝາຍຄວາມວ່າເຮົາສາມາດໃຊ້ .then ເປັນ chain ຕໍ່ກັນຫຼາຍໆ .then ເທົ່າທີ່ເຮົາຕ້ອງການໄດ້. ເຊິ່ງ result ຂອງ .then callback ກ່ອນໜ້າຈະຖືກ pass ເປັນໜຶ່ງ argument
ໃນ .then callback ຕໍ່ໄປ.
then chain
ໃນກໍລະນີຂອງຕົວຢ່າງ getImage ເຮົາສາມາດຂຽນ .then callback chain ໄດ້ເຊັ່ນກັນໂດຍເຮົາຈະທຳການ pass ໄຟລ໌ຮູບເຂົ້າໄປໃນ function ຕໍ່ໄປແທນທີ່ຈະໃຊ້ nested callback ຫຼາຍໆຊັ້ນເຊິ່ງມັນຈະເຮັດໃຫ້ code ຂອງເຮົາອ່ານງ່າຍຂຶ້ນ.
then chain in getImage example


Microtasks and (Macro)tasks

ມາຮອດບ່ອນນີ້ເຮົາກໍ່ໄດ້ຮູ້ວິທີສ້າງ promise ແລະ ວິທີຈັດການກັບ values ທີ່ໄດ້ຈາກ promise ກັນໄປແລ້ວ, ເຮົາລອງເພີ່ມ script ອີກໜ້ອຍໜຶ່ງແລ້ວທຳການ run ລອງເບິ່ງອີກຄັ້ງລອງເບິ່ງ.
microtask queue
ເຫັນຫຍັງບໍ່?🤯 ນັ້ນມັນແບ້ຫຍັງຫັ້ນ?
ທຳອິດເຮົາຈະ log ຄຳວ່າ Start! ອອກມາ, ເຊິ່ງບ່ອນນີ້ກໍ່ເປັນຜົນມາຈາກ code ໃນແຖວທຳອິດ. ຕໍ່ມາເຮົາຈະເຫັນວ່າມັນ log ຄຳວ່າ End! ແທນທີ່ຈະເປັນ value ຂອງ Promise.resolve ແຕ່ສ່ວນນີ້ພັດຖືກ log ອອກມາຫຼັງຈາກ End!. ເຮົາມາເບິ່ງນຳກັນຕໍ່ວ່າມັນເກີດຫຍັງຂຶ້ນໃນສ່ວນນີ້.
ມາຮອດບ່ອນນີ້ເຮົາກໍ່ເຫັນແລ້ວວ່າ promises ມັນເຮັດຫຍັງໄດ້ແນ່, ເຖິງ JavaScript ມັນຈະເປັນ single-thread ແຕ່ເຮົາກໍ່ສາມາດເພິ່ມຄວາມສາມາດແບບ asynchronous ໂດຍໃຊ້ Promise ໄດ້ເຊັ່ນກັນ.
ແຕ່ຮູ້ສຶກວ່າເຮົາໄດ້ເວົ້າເຖິງເລື່ອງນີ້ໄປແລ້ວໃນບົດຄວາມ EP1: Event loop?🤔, ແຕ່ເຮົາຍັງສາມາດໃຊ້ວິທີການແບບເດີມໆທີ່ເຮົາເຄີຍເຮັດຜ່ານມາເຊັ່ນ: setTimeout ໃນການສ້າງ asynchronous behavior ບາງປະເພດໄດ້ຫຼືບໍ່?🧐🤨🤯
ແມ່ນແລ້ວ, ແຕ່ໃນ Event loop ມີຄິວ(Queues)ຢູ່ 2 ປະເພດຄື: (macro)task queue (ບາງຄັ້ງກໍ່ເອີ້ນວ່າ task queue) ແລະ microtask queue. ເຊິ່ງ (macro)task queue ກໍ່ເປັນ queue ສຳລັບ (macro)tasks ແລະ microtask queue ກໍ່ເປັນ queue ສຳລັບ microtask.
ແລ້ວ (macro)task ແລະ microtask ແມ່ນຫຍັງ?, ມາທຳຄວາມເຂົ້າໃຈໄປພ້ອມໆກັນເລີຍ.

(macro)task and microtask
ຈາກຕາຕະລາງຈະເຫັນວ່າ Promise ຈະຢູ່ໃນ microtask list, ເມື່ອ Promise ທຳການ resolve ແລະ ໄດ້ເອີ້ນໃຊ້ then(), catch() ຫຼື finally() method. callback ທີ່ຢູ່ໃນ method ຈະຖືກເພີ່ມເຂົ້າໃນ microtask queue ໝາຍຄວາມວ່າບັນດາ callback ທີ່ຢູ່ໃນ then(), catch() ຫຼື finally() method ຈະບໍ່ຖືກ execute ທັນທີ, ແຕ່ໂດຍພື້ນຖານແລ້ວມັນຈະທຳການເພີ່ມ async ໃຫ້ JavaScript code ຂອງເຮົາໂດຍອັດຕະໂນມັດຢູ່ແລ້ວ.
ແລ້ວ then(), catch() ຫຼື finally() callback ຖືກ execute ຕອນໃດ? event loop ຈະເປັນຕົວທີ່ຈັດການກັບ priority ໃຫ້ແຕ່ລະ tasks ແຕກຕ່າງກັນດັ່ງນີ້:

  • function ທັງໝົດທີ່ຢູ່ໃນ call stack ຈະຖືກ execute, ເມື່ອມັນໄດ້ທຳການ return value ອອກມາ ພວກມັນຈະຖືກໂຍນອອກ(pop)ຈາກ call stack.
  • ເມື່ອ call stack ວ່າງ, microtasks ທັງໝົດທີ່ຢູ່ໃນ queue ຈະຖືກໂຍນມາທີ່ callstack ເທື່ອລະຕົວ ແລະ ທຳການ execute ຕໍ່ໄປ(ໂຕ Microtasks ເອງກໍ່ຍັງສາມາດ return microtasks ໃໝ່ອອກມາໄດ້ເຊັ່ນກັນ, ເຊິ່ງມັນຈະສ້າງ infinite microtask loop ໄດ້ຢ່າງມີປະສິດທິພາບ).
  • ຖ້າຫາກທັງ call stack ແລະ microtask queue ວ່າງ, event loop ຈະທຳການກວດເບິ່ງ tasks ທີ່ຍັງຄ້າງຢູ່ໃນ (macro)task queue ວ່າຍັງມີຫຼືບໍ່?, ຖ້າມີມັນກໍ່ຈະຖືກໂຍນມາທີ່ callstack ເພື່ອທຳການ execute ແລະ ເມື່ອ return value ອອກໄປແລ້ວມັນຈະທຳການໂຍນ task ອອກຈາກ callstack.

ເພື່ອໃຫ້ເຫັນພາບຫຼາຍຂຶ້ນ, ເຮົາມາເບິ່ງຕົວຢ່າງງ່າຍໆດ້ານລຸ່ມນຳກັນ:

  • Task1: function ທີ່ຖືກເພີ່ມເຂົ້າໄປໃນ call stack ຈະຖືກ execute ທັນທີ, ຕົວຢ່າງ: ການເອີ້ນໃຊ້ function ທີ່ຢູ່ໃນ code ຂອງເຮົາມັນຈະຖືກເອີ້ນໃຊ້ທັນທີ.
  • Task2, Task3, Task4: microtasks, ຕົວຢ່າງ: promise then callback ຫຼື task ທີ່ຖືກເພີ່ມໂດຍ queueMicrotask.
  • Task5, Task6: (macro)task, ຕົວຢ່າງ: setTimeout ຫຼື setImmediate callback. task queue and microtask queue with event loop ທຳອິດ Task1 ຈະທຳການ return value ແລະ ຈະຖືກໂຍນອອກຈາກ call stack. ຈາກນັ້ນ engine ຈະກວດເບິ່ງ tasks queue ທີ່ຢູ່ໃນ microtask queue. ເມື່ອ tasks ທັງໝົດຖືກເພີ່ມເຂົ້າໄປໃນ call stack ແລະ ຖືກໂຍນອອກໃນຕອນສຸດທ້າຍ engine ຈະທຳການກວດເບິ່ງ task ທີ່ຍັງເຫຼືອໃນ (macro)task queue ຕໍ່ ແລະ ຈະທຳການເພີ່ມເຂົ້າໄປໃນ call stack ແລະ ໂຍນອອກຫຼັງຈາກທີ່ມັນໄດ້ທຳການ return value ອອກມາ.

ດຽວເຮົາມາເບິ່ງຈາກ code ເລີຍດີກວ່າ.
call stack example
ໃນ code ດ້ານເທິງເຮົາມີ macro task ແມ່ນ setTimeout ແລະ microtask ແມ່ນ promise then() callback, ເມື່ອ engine ເຮັດວຽກມາຮອດແຖວຂອງ setTimeout function ຈະເກີດຫຍັງຂຶ້ນ? ເຮົາໄປເບິ່ງການເຮັດວຽກຂອງແຕ່ລະຂັ້ນຕອນນຳກັນເລີຍວ່າເຮົາຈະເຫັນຫຍັງໃນ log ແນ່.

ປລ: ໃນຕົວຢ່າງຕໍ່ໄປແມ່ນຈະສະແດງເຖິງບັນດາ methods ເຊັ່ນ: console.log, setTimeout ແລະ Promise.resolve ທີ່ຖືກເພີ່ມເຂົ້າໄປໃນ call stack. ເຊິ່ງພວກມັນເປັນ internal methods ດັ່ງນັ້ນເຮົາອາດຈະບໍ່ເຫັນມັນປາກົດຢູ່ໃນ stack traces, ແຕ່ບໍ່ຕ້ອງຕົກໃຈໄປ ຖ້າຫາກໃຊ້ debugger ແລ້ວບໍ່ເຫັນ ເນື່ອງຈາກເຮົາຕ້ອງການຍົກຕົວຢ່າງໃຫ້ເຫັນຄອນເຊັບຂອງການເຮັດວຽກຂອງມັນໂດຍທີ່ບໍ່ໄດ້ຂຽນ code ທີ່ຊັບຊ້ອນຫຍັງ.

ໃນແຖວທຳອິດ, engine ຈະພົບ console.log() method, ມັນຈະຖືກເພີ່ມເຂົ້າໄປໃນ call stack ຫຼັງຈາກນັ້ນມັນຈະ log ຄ່າ Start! ອອກມາຜ່ານ console ແລະ method ດັ່ງກ່າວຈະຖືກໂຍນອອກຈາກ call stack ແລ້ວ engine ກໍ່ຈະດຳເນີນການເຮັດວຽກຂອງມັນຕໍ່ໄປ.
engine encounter console.log
ຕໍ່ມາ engine ຈະພົບ setTimeout method, ເຊິ່ງມັນກໍ່ຈະຖືກເພີ່ມເຂົ້າໄປໃນ call stack. ຕົວ setTimeout method ແມ່ນເປັນ method ຂອງ browser ໂດຍທີ່ call back function ຂອງມັນ(() => console.log('Timeout'))ຈະຖືກເພີ່ມເຂົ້າໄປໃນ Web API ຈົນກວ່າ timer ເຮັດວຽກສຳເລັດ. ແຕ່ໃນທີ່ນີ້ເຮົາໄດ້ກຳນົດຄ່າໃຫ້ timer ມີຄ່າເປັນ 0 ແຕ່ call back ກໍ່ຍັງຄົງຖືກເພີ່ມລົງໄປໃນ Web API ກ່ອນຢູ່ດີ. ຫຼັງຈາກນັ້ນມັນຈະຖືກເພີ່ມເຂົ້າໄປໃນ (macro)task queue: setTimeout ເປັນ macro task.
setTimeout is macro task
step ຕໍ່ໄປ engine ຈະພົບ Promise.resolve() method. Promise.resolve() method ຈະຖືກເພີ່ມເຂົ້າໄປໃນ call stack, ຫຼັງຈາກທີ່ມັນທຳການ resolve ດ້ວຍ value Promise! ແລ້ວ, then callback function ຈະຖືກເພີ່ມເຂົ້າໄປໃນ microtask queue.
Promise added to call stack
ເມື່ອ engine ເຮັດວຽກມາຮອດແຖວສຸດທ້າຍມັນຈະພົບ console.log() method. ມັນຈະຖືກເພີ່ມເຂົ້າໄປໃນ call stack ທັນທີ, ຫຼັງຈາກມັນໄດ້ທຳການ log ຄ່າ End! ອອກມາທາງ console ແລ້ວ ມັນຈະຖືກໂຍນອອກຈາກ call stack ທັນທີ. ສ່ວນ engine ຈະເຮັດວຽກສ່ວນທີ່ເຫຼືອຕໍ່ໄປ.
console.log added to call stack
ມາຮອດບ່ອນນີ້ engine ຈະເຫັນວ່າ call stack ຂອງເຮົາວ່າງຢູ່. ເມື່ອ call stack ວ່າງມັນຈະໄປກວດເບິ່ງ task ທີ່ລໍຖ້າຢູ່ microtask queue ໃນ code ຕົວຢ່າງຂອງເຮົາຈະເຫັນວ່າຍັງມີ promise then callback ທີ່ຍັງລໍຖ້າຢູ່, ດັ່ງນັ້ນມັນຈຶ່ງຖືກໂຍນເຂົ້າໄປໃນ call stack ຫຼັງຈາກນັ້ນມັນຈະທຳການ resolve value ຂອງ promise ເຊິ່ງໃນຕົວຢ່າງແມ່ນມັນຈະ log ຄ່າ Promise!.
resolve value of promise
call stack ເຫັນວ່າຄິວວ່າງພໍດີມັນເລີຍເຂົ້າໄປກວດຢູ່ microtask queue ອີກຄັ້ງເພື່ອຊອກເບິ່ງວ່າຍັງມີ task ທີ່ຍັງຕໍ່ຄິວ(queue)ຫຼືບໍ່? ແຕ່ວ່າໃນກໍລະນີນີ້ microtask queue ກໍ່ວ່າງເຊັ່ນກັນ🤷🏻.
ເມື່ອຫາຢູ່ microtassk queue ບໍ່ເຫັນມັນຈຶ່ງທຳການໄປຫາຢູ່ (macro)task queue ຕໍ່, ເຊິ່ງໃນນີ້ຍັງມີ setTimeout callback ທີ່ຍັງຖ້າແລ້ວຖ້າອີກ ແຕ່ຕອນນີ້ເຖິງເວລາທີ່ຕ້ອງໂຍນເຂົ້າໄປໃນ call stack ແລ້ວ. callback function ທຳການ return console.log method ທີ່ທຳການ log ຄ່າ "Timeout!" ອອກໄປ. ຫຼັງຈາກນັ້ນ setTimeout callback ຈະຖືກໂຍນອອກຈາກ call stack.
setTimeout callback
ຈາກຕົວຢ່າງດ້ານເທິງກໍ່ນັບວ່າຈົບບໍລິບູນສຳລັບຕົວຢ່າງນີ້.


Async/Await

ES7 ໄດ້ແນະນຳວິທີໃໝ່ໃນການເພີ່ມ async behavior ໃນ JavaScript ແລະ ເຮັດໃຫ້ມັນເຮັດວຽກຮ່ວມກັບ promises ແບບງ່າຍໆ. ເຊິ່ງໃນນັ້ນຈະໃຊ້ async ແລະ await keyword ໃນການສ້າງ async functions ທີ່ສາມາດ return promise ອອກມາໄດ້, ສ່ວນວິທີການຈະເປັນແນວໃດນັ້ນ ໄປເບິ່ງນຳກັນເລີຍ.
ກ່ອນໜ້ານີ້ເຮົາໄດ້ເຫັນແລ້ວວ່າເຮົາສາມາດສ້າງ Promise ໄດ້ໂດຍໃຊ້ Promise object, ເຊິ່ງມັນກໍ່ຍັງມີອີກແບບອື່ນໆເຊັ່ນ: new Promise(() => {}), Promise.resolve, ຫຼື Promise.reject.
ແທນທີ່ຈະໃຊ້ Promise object ເຮົາສາມາດສ້າງ asynchronous functions ທີ່ return object ອອກມາໄດ້ເລີຍໂດຍທີ່ເຮົາບໍ່ຈຳເປັນຕ້ອງໄປຂຽນ Promise object ດ້ວຍຕົວເອງ.
asynchronous functions

ເຖິງແມ່ນວ່າໃນຄວາມເປັນຈິງແລ້ວ async functions ສາມາດ return promises ອອກມາໄດ້ນັ້ນກໍ່ນັບວ່າເປັນເລື່ອງດີ, ແຕ່ວ່າການທີ່ເຮົາຈະສາມາດສຳຜັດເຖິງຂຸມພະລັງທີ່ແທ້ຈິງຂອງ async functions ໄດ້ນັ້ນກໍ່ຄືຕອນທີ່ເຮົາໃຊ້ await keyword. ດ້ວຍ await keyword ເຮົາສາມາດລະງັບການເຮັດວຽກ(suspended)ຂອງ asynchronous function ໃນຂະນະທີ່ເຮົາລໍຖ້າ await value ຖືກ return ຈາກການ resolve promise ໄດ້ນັ້ນເອງ🥳. ຖ້າເຮົາຕ້ອງການ value ຂອງ promise ຫຼັງຈາກທີ່ຖືກ resolve ແລ້ວນັ້ນເຮົາກໍ່ພຽງແຕ່ເຮັດຄືຕົວຢ່າງທີ່ຜ່ານມາຄືໃຊ້ then() callback ແລະ ເຮົາຍັງສາມາດ assign variables ໃຫ້ກັບ await promise value ໄດ້ນຳ.
ມັນໝາຍຄວາມວ່າເຮົາສາມາດລະງັບການເຮັດວຽກ(suspended)ຂອງ async function ໄດ້ຫວາ? ຊິວ່າຈັ່ງຊັ້ນກໍ່ໄດ້, ແຕ່ວ່າມັນໝາຍຄວາມວ່າແນວໃດລ່ະ?.
ເຮົາໄປເບິ່ງ code ຕົວຢ່າງພ້ອມໆກັນເລີຍ:
async function
ເກີດຫຍັງຂຶ້ນກັບ code ຂອງເຮົາແນ່?
call stack with async function
ທຳອິດ engine ມັນຈະພົບກັບ console.log. ມັນຈະຖືກໂຍນເຂົ້າໄປໃນ call stack ຫຼັງຈາກນັ້ນມັນຈະທຳການ log ຄ່າ Before function! ອອກມາ.
call stack with async function2
ຈາກນັ້ນເຮົາໄດ້ທຳການເອີ້ນໃຊ້ async function ທີ່ຊື່ວ່າ myFunc(), ຫຼັງຈາກນັ້ນຕົວ myFunc() function ກໍ່ຈະເຮັດວຽກ. ໃນແຖວທຳອິດຂອງ function body ເຮົາໄດ້ທຳການເອີ້ນໃຊ້ console.log ອີກຄັ້ງ, ເທື່ອນີ້ມັນຈະ log ຄ່າ In function!, ເຊິ່ງ console.log ຈະຖືກໂຍນເຂົ້າໄປໃນ call stack, log ຄ່າອອກມາ ແລະ ຖືກໂຍນອອກຈາກ call stack ຕາມ step ເດີມໆ.
myFunc on call stack
ໃນ function body ຍັງຄົງທຳການ execute ຕໍ່ໄປ, ເຊິ່ງເຮົາຈະເຫັນພະເອກຂອງເຮົາຄື await keyword ຢູ່ໃນແຖວທີ 2 🥳🥳🥳.

ສິ່ງທຳອິດທີ່ຈະເກີດຂຶ້ນກໍ່ຄື: value ຈະລໍຖ້າ execute, ເຊິ່ງໃນກໍລະນີນີ້ແມ່ນ one function. ມັນຈະຖືກໂຍນເຂົ້າໄປໃນ call stack ແລະ ມັນຈະທຳການ return value ຂອງ resolve promise ອອກມາ. ເມື່ອ promise ຖືກ resolve ແລ້ວ ແລະ one ໄດ້ return value ອອກມາ, ຈາກນັ້ນ engine ກໍ່ຈະພົບ await keyword.

ເມື່ອມັນພົບ await keyword ແລ້ວ, async function ຈະຖືກລະງັບການເຮັດວຽກ(suspended). ການ execute ພາຍໃນ function body ຈະທຳການຢຸດຊົ່ວຄາວ(paused) ແລະ ສ່ວນທີ່ເຫຼືອໃນ async function ຈະໄປ run ຢູ່ microtask ແທນ task ທຳມະດາແທນ.
finally call console.log
ຕອນນີ້ async function ທີ່ຊື່ວ່າ myFunc ຈະຖືກລະງັບການເຮັດວຽກ(suspended)ໃນກໍລະນີທີ່ທັນພົບ await keyword, engine ຈະໂດດອອກຈາກ async function ແລະ ທຳການ execute code ຕໍ່ໄປໃນບໍລິບົດຂອງການ execution ທີ່ async function ຖືກເອີ້ນໃຊ້. ເຊິ່ງໃນກໍລະນີນີ້ແມ່ນ global execution context.
after await
ສຸດທ້າຍກໍ່ບໍ່ເຫຼືອ task ໃດທີ່ຢູ່ໃນ global execution context. event loop ຈະທຳການກວດເບິ່ງວ່າພົບ microtasks ທີ່ລໍຖ້າ queue ຢູ່ຫຼືບໍ່? ແລະ ມັນກໍ່ຍັງມີ async myFunc function ທີ່ຢູ່ໃນ queue ຫຼັງຈາກທີ່ທຳການ resolve value ຂອງ one. myFunc ຈະຖືກເພີ່ມເຂົ້າໄປໃນ call stack ອີກຄັ້ງ ແລະ ທຳການ run ຕໍ່ຈາກທີ່ຄ້າງໄວ້ກ່ອນໜ້ານີ້.

ໃນທີ່ສຸດ variable res ກໍ່ໄດ້ value ອອກມາ, ເຊິ່ງມັນກໍ່ເປັນ value ທີ່ຖືກ resolve ຈາກ promise ນັ້ນກໍ່ຄື one ນັ້ນເອງ. ເຮົາໄດ້ທຳການເອີ້ນໃຊ້ console.log ດ້ວຍ value ຂອງ res ໃນກໍລະນີນີ້ແມ່ນ One!, ເຊິ່ງ One! ຈະຖືກ log ອອກມາຜ່ານ console ແລະ ມັນຈະຖືກໂຍນອອກຈາກ call stack.

ມາຮອດຕອນນີ້ກໍ່ເປັນອັນຈົບຊີຣີ່ຂອງ JavaScript ພື້ນຖານ(ແບບສຸດໆ), ຈາກຫຼາຍໆຕົວຢ່າງຜ່ານມາເຮົາກໍ່ຈະເຫັນພາບແລ້ວວ່າພາສາ JavaScript ເອງມັນກໍ່ມີຈຸດທີໜ້າສົນໃນໃນຫຼາຍໆເລື່ອງ ແລະ ຫຼາຍໆ feature ຢູ່ບໍ່ໜ້ອຍ, ຖ້າເຮົາເຂົ້າໃຈຂະບວນການເຮັດວໜກຂອງມັນເຮົາກໍ່ຈະສາມາດຂຽນ code ໄດ້ຢ່າງມີປະສິດທິພາບຫຼາຍຂຶ້ນ ແຕ່ທັງນີ້ກໍ່ຂຶ້ນກັບຄວາມຮູ້ດ້ານອື່ນໆນຳ.


ອ້າງອີງ: ⭐️🎀 JavaScript Visualized: Promises & Async/Await

Top comments (0)