Asynchronous JavaScript

មាតិកា

  1. ហេតុអ្វីកូដត្រូវរង់ចាំ
  2. Event Loop ដោយសង្ខេប
  3. Callback
  4. Promise
  5. async និង await
  6. ការដំណើរការ Task ជាមួយគ្នាដោយ Promise.all
  7. fetch៖ ការនិយាយជាមួយ Server
  8. Project៖ Repository GitHub របស់ Dara
  9. សាកល្បងដោយខ្លួនឯង

1. ហេតុអ្វីកូដត្រូវរង់ចាំ

រហូតមកដល់ពេលនេះ កូដនីមួយៗរបស់អ្នកបានចប់មុនបន្ទាត់បន្ទាប់ចាប់ផ្តើម។ នោះដំណើរការបានល្អនៅពេលការងារលឿន។ ប៉ុន្តែមានរឿងខ្លះ យឺត៖ ការផ្ទុកទិន្នន័យពី server ការអានឯកសារធំ ការរង់ចាំ timer។ ដូចដែលអ្នកបានរៀនក្នុង របៀបដែលវេបដំណើរការ network request ធ្វើដំណើរឆ្លងអ៊ីនធឺណិតទៅហើយត្រឡប់មកវិញ ដែលអាចចំណាយពីពីរបីមិល្លីវិនាទីរហូតដល់ច្រើនវិនាទី។

ប្រសិនបើ JavaScript គ្រាន់តែឈប់ហើយរង់ចាំ ទំព័រទាំងមូលនឹងគាំង។ គ្មានការ click គ្មានការ scroll គ្មានអ្វីទាំងអស់។ JavaScript មាន thread សម្រាប់ដំណើរការចម្បងតែមួយ ដូច្នេះវាមិនត្រូវ block វាឡើយ។

ដំណោះស្រាយគឺកូដ asynchronous៖ ចាប់ផ្តើម task ដែលយឺត បន្តធ្វើការ ហើយដំណើរការកូដមួយផ្នែកនៅពេលក្រោយ នៅពេលលទ្ធផលរួចរាល់។ ជំពូកនេះបង្ហាញឧបករណ៍សម្រាប់រឿងនោះ ចាប់ពីចាស់បំផុតទៅថ្មីបំផុត។

2. Event Loop ដោយសង្ខេប

ស្រមៃមើលចុងភៅម្នាក់ក្នុងផ្ទះបាយតូចមួយ ធ្វើការតែម្នាក់ឯង។ គាត់ដាក់បាយលើចង្ក្រាន ហើយជំនួសឲ្យការសម្លឹងមើលឆ្នាំង គាត់កាត់បន្លែ។ នៅពេល timer រោទិ៍ គាត់ត្រឡប់ទៅរកបាយវិញ។ ចុងភៅ (thread) ធ្វើរឿងតែមួយក្នុងមួយពេល ប៉ុន្តែមិនខ្ជះខ្ជាយពេលរង់ចាំទេ។

JavaScript ធ្វើការតាមរបៀបដូចគ្នា។ browser (ឬ Node) ដោះស្រាយការងារយឺតនៅផ្ទៃខាងក្រោយ។ នៅពេលវាចប់ វាដាក់ function ដែលកំពុងរង់ចាំរបស់អ្នកទៅក្នុង queue។ event loop យក function ពី queue តែនៅពេលកូដបច្ចុប្បន្នរបស់អ្នកចប់ទាំងស្រុងប៉ុណ្ណោះ។

អ្នកអាចឃើញវាដោយ setTimeout ដែលដំណើរការ function បន្ទាប់ពីពន្យារពេលជាមិល្លីវិនាទី៖

console.log("1. start");

setTimeout(() => {
  console.log("3. timer");
}, 0);

console.log("2. end");

លទ្ធផលគឺ 1. start, 2. end បន្ទាប់មក 3. timer។ សូម្បីតែពន្យារពេល 0 ក៏ function របស់ timer រង់ចាំក្នុង queue រហូតដល់កូដបច្ចុប្បន្នចប់។ ចងចាំច្បាប់នេះ៖ function ដែលអ្នកប្រគល់សម្រាប់ពេលក្រោយ មិនដែលដំណើរការនៅកណ្តាលកូដបច្ចុប្បន្នរបស់អ្នកទេ។

3. Callback

ឧបករណ៍ដំបូងគឺ callback៖ function ដែលអ្នកបញ្ជូនទៅ function មួយទៀត ដើម្បីត្រូវបានហៅនៅពេលការងារចប់។ អ្នកបានប្រើ callback រួចហើយក្នុង addEventListener និង setTimeout។

function getUser(id, callback) {
  setTimeout(() => {
    callback({ id, name: "Dara" });
  }, 100);
}

getUser(1, (user) => {
  console.log(user.name); // "Dara"
});

callback ដំណើរការបាន ប៉ុន្តែវាច្របូកច្របល់នៅពេលជំហានពឹងផ្អែកលើគ្នា។ ផ្ទុក user បន្ទាប់មក project របស់គាត់ បន្ទាប់មកព័ត៌មានលម្អិតនៃ project មួយ ហើយជំហាននីមួយៗ nest នៅក្នុងជំហានមុន៖

getUser(1, (user) => {
  getProjects(user.id, (projects) => {
    getDetails(projects[0].id, (details) => {
      // កាន់តែជ្រៅទៅៗ...
    });
  });
});

រូបរាងនេះមានឈ្មោះហៅក្រៅថា "callback hell"។ ការដោះស្រាយ error ក៏ពិបាកដែរ ព្រោះកម្រិតនីមួយៗត្រូវការការពិនិត្យផ្ទាល់ខ្លួន។ promise ត្រូវបានបង្កើតឡើងដើម្បីដោះស្រាយបញ្ហានេះ។

4. Promise

promise គឺជា object ដែលតំណាងឲ្យ value ដែលអ្នកមិនទាន់មាន។ វាស្ថិតក្នុងស្ថានភាពមួយក្នុងចំណោមបីជានិច្ច៖

ស្ថានភាពអត្ថន័យ
pendingការងារកំពុងដំណើរការនៅឡើយ
fulfilledវាចប់ហើយ ហើយមានលទ្ធផល
rejectedវាបរាជ័យ ហើយមាន error

នេះគឺជា function ដែលត្រឡប់ promise។ វាជោគជ័យសម្រាប់ id project ដែលស្គាល់ ហើយបរាជ័យសម្រាប់ id ផ្សេងទៀត៖

function loadProject(id) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (id > 3) {
        reject(new Error(`Project ${id} not found`));
      } else {
        resolve({ id, title: `Project ${id}` });
      }
    }, 100);
  });
}

ហៅ resolve(value) សម្រាប់ភាពជោគជ័យ និង reject(error) សម្រាប់ភាពបរាជ័យ។ អ្នកហៅប្រើ .then() សម្រាប់លទ្ធផល និង .catch() សម្រាប់ error ហើយ .finally() ដំណើរការក្នុងករណីទាំងពីរ៖

loadProject(1)
  .then((project) => {
    console.log(project.title); // "Project 1"
    return loadProject(9);      // ការត្រឡប់ promise បន្តខ្សែសង្វាក់
  })
  .then(() => console.log("never reached"))
  .catch((error) => console.log("Caught:", error.message))
  .finally(() => console.log("Done"));

ខ្សែសង្វាក់នេះរាបស្មើ ជំនួសឲ្យ nest ហើយ .catch() តែមួយនៅចុងបញ្ចប់ដោះស្រាយការបរាជ័យពីជំហានណាមួយខាងលើវា។

5. async និង await

promise ល្អ ប៉ុន្តែ async និង await ធ្វើឲ្យវាអានដូចកូដធម្មតាពីលើចុះក្រោម។ ដាក់ async មុន function និង await មុនអ្វីដែលត្រឡប់ promise។ function ផ្អាកនៅ await (ដោយមិន block អ្វីផ្សេងទៀត) រហូតដល់ promise ត្រូវបានសម្រេច៖

async function main() {
  try {
    const project = await loadProject(2);
    console.log(project.title); // "Project 2"

    await loadProject(7); // reject ដូច្នេះបន្ទាត់បន្ទាប់មិនដំណើរការទេ
  } catch (error) {
    console.log("Caught:", error.message);
  }
}

main();

ច្បាប់បី៖

  • await ដំណើរការតែនៅក្នុង function async ប៉ុណ្ណោះ (ឬនៅកម្រិតលើបំផុតនៃ module ដែលអ្នកកំពុងប្រើឥឡូវនេះ)។
  • function async ត្រឡប់ promise ជានិច្ច។
  • ប្រើ try / catch ធម្មតាដើម្បីដោះស្រាយ error ដូចដែលជំពូកបន្ទាប់ពន្យល់ឲ្យកាន់តែស៊ីជម្រៅ។

6. ការដំណើរការ Task ជាមួយគ្នាដោយ Promise.all

បន្ទាត់ await ពីរជាប់គ្នាដំណើរការមួយ បន្ទាប់ ពីមួយទៀត។ ប្រសិនបើ task មិនពឹងផ្អែកលើគ្នា នោះខ្ជះខ្ជាយពេល។ ចាប់ផ្តើមទាំងពីរក្នុងពេលតែមួយដោយ Promise.all៖

const [first, third] = await Promise.all([
  loadProject(1),
  loadProject(3),
]);

console.log(first.title, third.title); // "Project 1 Project 3"

Promise.all ទទួល array នៃ promise ហើយផ្តល់ array នៃលទ្ធផលតាមលំដាប់ដូចគ្នា។ វាចប់នៅពេល promise ទាំងអស់ ជោគជ័យ ដោយចំណាយពេលប្រហែលស្មើនឹង promise ដែលយឺតបំផុត។ ប្រសិនបើមួយណា បរាជ័យ ទាំងមូលនឹង reject។ នៅពេលអ្នកចង់បានលទ្ធផលទាំងអស់ដោយមិនគិតពីការបរាជ័យ សូមប្រើ Promise.allSettled ជំនួស។

7. fetch៖ ការនិយាយជាមួយ Server

function fetch ដែលភ្ជាប់មកជាមួយ browser ផ្ញើ HTTP request ហើយត្រឡប់ promise។ Node ក៏មានវាដែរ។ វាជាគំនិត request-និង-response ដូចគ្នាពីមូលដ្ឋានគ្រឹះនៃវេប ប៉ុន្តែជាកូដ៖

const response = await fetch("https://api.github.com/users/octocat/repos");
const repos = await response.json();

សូមកត់សម្គាល់ថាមាន await ពីរ។ ទីមួយ resolve នៅពេល header របស់ response មកដល់។ ទីពីរ response.json() អាន body ហើយបំប្លែងអត្ថបទ JSON ទៅជា object របស់ JavaScript។

មានអន្ទាក់មួយនៅទីនេះដែលចាប់ស្ទើរតែគ្រប់គ្នា។ fetch reject តែនៅពេលបណ្តាញខ្លួនវាបរាជ័យប៉ុណ្ណោះ។ response 404 Not Found ឬ 500 នៅតែជា fetch "ជោគជ័យ"! អ្នកត្រូវតែពិនិត្យវា៖

if (!response.ok) {
  throw new Error(`Request failed with status ${response.status}`);
}

response.ok ជា true សម្រាប់ status code ពី 200 ដល់ 299។ ពិនិត្យវាជានិច្ចមុនពេលអ្នកហៅ json()។

8. Project៖ Repository GitHub របស់ Dara

ឥឡូវបន្ថែម feature ចុងក្រោយនៃទំព័រ៖ បញ្ជី repository សាធារណៈរបស់ Dara ដែលផ្ទុកពី GitHub API។ address គឺ https://api.github.com/users/<username>/repos។ ជំនួស your-github-username ខាងក្រោមដោយឈ្មោះគណនីពិតប្រាកដ (របស់អ្នក ឬ octocat)។

ដំបូង ក្នុង index.html៖

<section id="repos">
  <h2>My GitHub Repositories</h2>
  <p id="repos-status" role="status"></p>
  <ul id="repos-list"></ul>
</section>

បន្ទាប់មកក្នុង module ថ្មី js/repos.js៖

const USERNAME = "your-github-username";
const url = `https://api.github.com/users/${USERNAME}/repos?sort=updated&per_page=6`;

const status = document.querySelector("#repos-status");
const list = document.querySelector("#repos-list");

async function loadRepos() {
  status.textContent = "Loading repositories...";
  list.replaceChildren();

  try {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`GitHub returned status ${response.status}`);
    }

    const repos = await response.json();
    if (repos.length === 0) {
      status.textContent = "No public repositories yet.";
      return;
    }

    for (const repo of repos) {
      const item = document.createElement("li");
      const link = document.createElement("a");
      link.href = repo.html_url;
      link.textContent = repo.name;
      item.append(link, ` · ${repo.language ?? "No language"}`);
      list.append(item);
    }
    status.textContent = "";
  } catch (error) {
    status.textContent = `Could not load repositories. ${error.message}`;
  }
}

loadRepos();

ផ្ទុកវាពី main.js ដោយ import "./repos.js"; ឬបន្ថែម tag <script type="module"> ទីពីរ។

នេះគឺជារូបរាងស្តង់ដារនៃកូដផ្ទុកទិន្នន័យណាមួយ ហើយវាដោះស្រាយ ស្ថានភាពទាំងបួន ដែលអ្នកប្រើអាចឃើញ៖ កំពុងផ្ទុក ជោគជ័យ ទទេ និង error។ បើគ្មានទាំងនេះ អ្នកទស្សនាដែលសម្លឹងមើលផ្នែកទទេមិនអាចដឹងថាទំព័រខូច ឬគ្រាន់តែយឺតនោះទេ។

រឿងពីរអំពី GitHub API ដែលត្រូវដឹង៖

  • កំណត់អត្រា (Rate limit)។ ដោយមិនចូលគណនី GitHub អនុញ្ញាតប្រហែល 60 request ក្នុងមួយម៉ោងពី address អ៊ីនធឺណិតតែមួយ។ ប្រសិនបើអ្នក reload ច្រើនដងពេលសាកល្បង អ្នកអាចទទួលបាន status 403។ សារ error របស់អ្នកនឹងលេចឡើង ដែលបង្ហាញថាស្ថានភាព error ដំណើរការ។ រង់ចាំ ហើយសាកល្បងម្តងទៀត។
  • អត្ថបទសុវត្ថិភាព។ ឈ្មោះ repository និងភាសាមកពីក្រៅកូដរបស់អ្នក ដូច្នេះយើងដាក់វាក្នុង textContent និង href មិនដែលក្នុង innerHTML ឡើយ។

បើក DevTools ជ្រើសរើស tab Network ហើយ reload។ អ្នកនឹងឃើញ request ទៅ api.github.com status code របស់វា និង JSON ក្នុង tab Response។ បើក throttling "Offline" ហើយ reload ដើម្បីមើលស្ថានភាព error របស់អ្នក។

9. សាកល្បងដោយខ្លួនឯង

  1. ទស្សន៍ទាយលំដាប់។ សរសេរ console.log បីជាមួយ setTimeout(..., 0) នៅកណ្តាល។ ទាយលទ្ធផល បន្ទាប់មកដំណើរការក្នុង Node។
  2. ពី callback ទៅ promise។ យក getUser ពីផ្នែកទី 3 ហើយសរសេរឡើងវិញឲ្យវាត្រឡប់ promise។ ប្រើវាជាមួយ .then()។
  3. ប្រើ async/await។ សរសេរខ្សែសង្វាក់ promise របស់អ្នកឡើងវិញដោយ async, await និង try / catch។ ធ្វើឲ្យ id មួយបរាជ័យដោយចេតនា។
  4. វាស់ Promise.all។ បង្កើត wait(ms) ដោយ new Promise((resolve) => setTimeout(resolve, ms))។ វាស់ពេល await wait(500) ពីរជាប់គ្នា បន្ទាប់មកក្នុង Promise.all។ ប្រើ console.time និង console.timeEnd។
  5. បង្ហាញ 404។ ប្តូរ username ទៅឈ្មោះដែលមិនមាន ឬបន្ថែមអក្ខរាវិរុទ្ធខុសក្នុង URL។ បញ្ជាក់ថាទំព័ររបស់អ្នកបង្ហាញសារ error។
  6. បញ្ចប់ project។ សាងសង់បញ្ជី repos។ តម្រៀបវាតាម stargazers_count ហើយបង្ហាញចំនួននៅក្បែរឈ្មោះនីមួយៗ។

បន្ទាប់ យើងនឹងមើលឲ្យកាន់តែជិតអំពីអ្វីដែលកើតឡើងនៅពេលកូដបរាជ័យ៖ ប្រភេទ error, try / catch និងរបៀប debug ដោយ console និង debugger របស់ DevTools។

ទិញកាហ្វេជូនខ្ញុំ

ចូលចិត្តមេរៀននេះមែនទេ? សូមពិចារណាទិញកាហ្វេជូនខ្ញុំមួយកែវ ដើម្បីគាំទ្រការបង្កើតមាតិកាឥតគិតថ្លៃបន្ថែមទៀត!