Fetch API

Codelab นี้เป็นส่วนหนึ่งของหลักสูตรการฝึกอบรมการพัฒนา Progressive Web App ซึ่งพัฒนาโดยทีมฝึกอบรมของ Google Developers คุณจะได้รับประโยชน์สูงสุดจากหลักสูตรนี้หากทำตาม Codelab ตามลำดับ

ดูรายละเอียดทั้งหมดเกี่ยวกับหลักสูตรได้ที่ภาพรวมการพัฒนา Progressive Web App

บทนำ

ใน Lab นี้ เราจะแนะนำวิธีใช้ Fetch API ซึ่งเป็นอินเทอร์เฟซที่เรียบง่ายสำหรับการดึงข้อมูลทรัพยากร และเป็น API ที่ได้รับการปรับปรุงจาก XMLHttpRequest API

สิ่งที่คุณจะได้เรียนรู้

  • วิธีใช้ Fetch API เพื่อขอทรัพยากร
  • วิธีส่งคำขอ GET, HEAD และ POST ด้วยการดึงข้อมูล
  • วิธีอ่านและตั้งค่าส่วนหัวที่กำหนดเอง
  • การใช้งานและข้อจำกัดของ CORS

สิ่งที่ควรทราบ

  • JavaScript และ HTML พื้นฐาน
  • คุ้นเคยกับแนวคิดและไวยากรณ์พื้นฐานของ Promise ใน ES2015

สิ่งที่คุณจะต้องมี

  • คอมพิวเตอร์ที่มีสิทธิ์เข้าถึงเทอร์มินัล/เชลล์
  • การเชื่อมต่ออินเทอร์เน็ต
  • เบราว์เซอร์ที่รองรับ Fetch
  • โปรแกรมแก้ไขข้อความ
  • Node และ npm

หมายเหตุ: แม้ว่าปัจจุบันเบราว์เซอร์บางตัวจะไม่รองรับ Fetch API แต่ก็มี polyfill

ดาวน์โหลดหรือโคลนที่เก็บ pwa-training-labs จาก GitHub และติดตั้ง Node.js เวอร์ชัน LTS หากจำเป็น

เปิดบรรทัดคำสั่งของคอมพิวเตอร์ ไปที่ไดเรกทอรี fetch-api-lab/app/ แล้วเริ่มเซิร์ฟเวอร์การพัฒนาซอฟต์แวร์ภายใน

cd fetch-api-lab/app
npm install
node server.js

คุณสิ้นสุดการทำงานของเซิร์ฟเวอร์ได้ทุกเมื่อด้วย Ctrl-c

เปิดเบราว์เซอร์ แล้วไปที่ localhost:8081/ คุณควรเห็นหน้าเว็บที่มีปุ่มสำหรับส่งคำขอ (ปุ่มจะยังใช้งานไม่ได้)

หมายเหตุ: ยกเลิกการลงทะเบียน Service Worker และล้างแคชทั้งหมดของ Service Worker สำหรับ localhost เพื่อไม่ให้รบกวน Lab ในเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome คุณทำได้โดยคลิกล้างข้อมูลเว็บไซต์จากส่วนล้างพื้นที่เก็บข้อมูลของแท็บแอปพลิเคชัน

เปิดโฟลเดอร์ fetch-api-lab/app/ ในโปรแกรมแก้ไขข้อความที่ต้องการ app/ โฟลเดอร์คือที่ที่คุณจะสร้าง Lab

โฟลเดอร์นี้ประกอบด้วย

  • echo-servers/ มีไฟล์ที่ใช้สำหรับเรียกใช้เซิร์ฟเวอร์ทดสอบ
  • examples/ มีทรัพยากรตัวอย่างที่เราใช้ในการทดลองกับ Fetch
  • js/main.js คือ JavaScript หลักของแอป และเป็นที่ที่คุณจะเขียนโค้ดทั้งหมด
  • index.html คือหน้า HTML หลักสำหรับเว็บไซต์/แอปพลิเคชันตัวอย่าง
  • package-lock.json และ package.json เป็นไฟล์การกำหนดค่าสำหรับเซิร์ฟเวอร์การพัฒนาและการขึ้นต่อกันของเซิร์ฟเวอร์ Echo
  • server.js เป็นเซิร์ฟเวอร์การพัฒนาโหนด

Fetch API มีอินเทอร์เฟซที่ค่อนข้างเรียบง่าย ส่วนนี้อธิบายวิธีเขียนคำขอ HTTP พื้นฐานโดยใช้ฟังก์ชัน Fetch

ดึงข้อมูลไฟล์ JSON

ใน js/main.js ปุ่มดึงข้อมูล JSON ของแอปจะเชื่อมต่อกับฟังก์ชัน fetchJSON

อัปเดตฟังก์ชัน fetchJSON เพื่อขอไฟล์ examples/animals.json และบันทึกการตอบกลับโดยทำดังนี้

function fetchJSON() {
  fetch('examples/animals.json')
    .then(logResult)
    .catch(logError);
}

บันทึกสคริปต์แล้วรีเฟรชหน้าเว็บ คลิกดึงข้อมูล JSON คอนโซลควรบันทึกการตอบกลับของคำสั่งดึงข้อมูล

คำอธิบาย

fetch เมธอดจะยอมรับเส้นทางของทรัพยากรที่เราต้องการดึงข้อมูลเป็นพารามิเตอร์ ในกรณีนี้คือ examples/animals.json fetch จะแสดงผล Promise ที่แปลงเป็นออบเจ็กต์ Response หาก Promise แก้ไขได้ ระบบจะส่งการตอบกลับไปยังฟังก์ชัน logResult หาก Promise ปฏิเสธ catch จะเข้ามาแทนที่และส่งต่อข้อผิดพลาดไปยังฟังก์ชัน logError

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

ทดสอบคำตอบที่ไม่ถูกต้อง

ตรวจสอบการตอบกลับที่บันทึกไว้ในคอนโซล จดค่าของพร็อพเพอร์ตี้ status, url และ ok

แทนที่ทรัพยากร examples/animals.json ใน fetchJSON ด้วย examples/non-existent.json ตอนนี้ฟังก์ชัน fetchJSON ที่อัปเดตแล้วควรมีลักษณะดังนี้

function fetchJSON() {
  fetch('examples/non-existent.json')
    .then(logResult)
    .catch(logError);
}

บันทึกสคริปต์แล้วรีเฟรชหน้าเว็บ คลิกดึงข้อมูล JSON อีกครั้งเพื่อลองดึงข้อมูลทรัพยากรที่ไม่มีอยู่

สังเกตว่าการดึงข้อมูลเสร็จสมบูรณ์และไม่ได้ทริกเกอร์catchบล็อก ตอนนี้ให้ค้นหาพร็อพเพอร์ตี้ status, URL และ ok ของคำตอบใหม่

ค่าในไฟล์ทั้ง 2 ควรแตกต่างกัน (คุณเข้าใจไหมว่าทำไม) หากคุณได้รับข้อผิดพลาดในคอนโซล ค่าตรงกับบริบทของข้อผิดพลาดหรือไม่

คำอธิบาย

เหตุใดการตอบกลับที่ไม่สำเร็จจึงไม่เปิดใช้งานcatchบล็อก นี่เป็นหมายเหตุสำคัญสำหรับ Fetch และ Promise ซึ่งการตอบกลับที่ไม่ถูกต้อง (เช่น 404) จะยังคงแก้ไขได้ สัญญาการดึงข้อมูลจะปฏิเสธก็ต่อเมื่อคำขอไม่สามารถดำเนินการให้เสร็จสมบูรณ์ได้ ดังนั้นคุณต้องตรวจสอบความถูกต้องของการตอบกลับเสมอ เราจะตรวจสอบคำตอบในส่วนถัดไป

สำหรับข้อมูลเพิ่มเติม

ตรวจสอบความถูกต้องของคำตอบ

เราต้องอัปเดตรหัสเพื่อตรวจสอบความถูกต้องของการตอบกลับ

ใน main.js ให้เพิ่มฟังก์ชันเพื่อตรวจสอบคำตอบ

function validateResponse(response) {
  if (!response.ok) {
    throw Error(response.statusText);
  }
  return response;
}

จากนั้นแทนที่ fetchJSON ด้วยโค้ดต่อไปนี้

function fetchJSON() {
  fetch('examples/non-existent.json')
    .then(validateResponse)
    .then(logResult)
    .catch(logError);
}

บันทึกสคริปต์แล้วรีเฟรชหน้าเว็บ คลิกดึงข้อมูล JSON ตรวจสอบคอนโซล ตอนนี้คำตอบสำหรับ examples/non-existent.json ควรทริกเกอร์บล็อก catch

แทนที่ examples/non-existent.json ในฟังก์ชัน fetchJSON ด้วย examples/animals.json เดิม ตอนนี้ฟังก์ชันที่อัปเดตแล้วควรมีลักษณะดังนี้

function fetchJSON() {
  fetch('examples/animals.json')
    .then(validateResponse)
    .then(logResult)
    .catch(logError);
}

บันทึกสคริปต์แล้วรีเฟรชหน้าเว็บ คลิกดึงข้อมูล JSON คุณควรเห็นว่าระบบบันทึกการตอบกลับได้สำเร็จเช่นเดิม

คำอธิบาย

ตอนนี้เราได้เพิ่มการตรวจสอบ validateResponse แล้ว คำตอบที่ไม่ดี (เช่น 404) จะทำให้เกิดข้อผิดพลาดและ catch จะเข้ามาแทนที่ ซึ่งจะช่วยให้เราจัดการการตอบกลับที่ไม่สำเร็จและป้องกันไม่ให้การตอบกลับที่ไม่คาดคิดแพร่กระจายลงในห่วงโซ่การดึงข้อมูล

อ่านคำตอบ

การตอบกลับของ Fetch จะแสดงเป็น ReadableStreams (ข้อกำหนดของสตรีม) และต้องอ่านเพื่อเข้าถึงเนื้อหาของการตอบกลับ ออบเจ็กต์การตอบกลับมีเมธอดสำหรับดำเนินการนี้

ใน main.js ให้เพิ่มฟังก์ชัน readResponseAsJSON ด้วยโค้ดต่อไปนี้

function readResponseAsJSON(response) {
  return response.json();
}

จากนั้นแทนที่ฟังก์ชัน fetchJSON ด้วยโค้ดต่อไปนี้

function fetchJSON() {
  fetch('examples/animals.json') // 1
  .then(validateResponse) // 2
  .then(readResponseAsJSON) // 3
  .then(logResult) // 4
  .catch(logError);
}

บันทึกสคริปต์แล้วรีเฟรชหน้าเว็บ คลิกดึงข้อมูล JSON ตรวจสอบคอนโซลเพื่อให้แน่ใจว่าระบบบันทึก JSON จาก examples/animals.json (แทนที่จะเป็นออบเจ็กต์การตอบกลับ)

คำอธิบาย

มาดูสิ่งที่เกิดขึ้นกัน

ขั้นตอนที่ 1 เรียกใช้ Fetch ในทรัพยากร examples/animals.json Fetch จะแสดงผล Promise ที่เปลี่ยนเป็นออบเจ็กต์ Response เมื่อ Promise แก้ไขแล้ว ระบบจะส่งออบเจ็กต์การตอบกลับไปยัง validateResponse

ขั้นตอนที่ 2 validateResponse ตรวจสอบว่าการตอบกลับถูกต้องหรือไม่ (เป็นการตอบกลับ 200 หรือไม่) หากไม่เป็นเช่นนั้น ระบบจะแสดงข้อผิดพลาด ข้ามบล็อก then ที่เหลือ และทริกเกอร์บล็อก catch ซึ่งถือเป็นสิ่งสำคัญอย่างยิ่ง หากไม่มีการตรวจสอบนี้ ระบบจะส่งคำตอบที่ไม่ดีไปยังเชนและอาจทำให้โค้ดในภายหลังที่อาจต้องอาศัยการรับคำตอบที่ถูกต้องใช้งานไม่ได้ หากการตอบกลับถูกต้อง ระบบจะส่งไปยัง readResponseAsJSON

ขั้นตอนที่ 3 readResponseAsJSON อ่านเนื้อหาของการตอบกลับโดยใช้วิธี Response.json() เมธอดนี้จะแสดงผล Promise ที่แปลงเป็น JSON เมื่อคำสัญญานี้ได้รับการแก้ไข ระบบจะส่งข้อมูล JSON ไปยัง logResult (หาก Promise จาก response.json() ปฏิเสธ ระบบจะเรียกใช้บล็อก catch)

ขั้นตอนที่ 4 สุดท้าย logResult จะบันทึกข้อมูล JSON จากคำขอดั้งเดิมไปยัง examples/animals.json

สำหรับข้อมูลเพิ่มเติม

Fetch ไม่ได้จำกัดอยู่แค่ JSON ในตัวอย่างนี้ เราจะดึงข้อมูลรูปภาพและต่อท้ายในหน้าเว็บ

ใน main.js ให้เขียนฟังก์ชัน showImage ด้วยโค้ดต่อไปนี้

function showImage(responseAsBlob) {
  const container = document.getElementById('img-container');
  const imgElem = document.createElement('img');
  container.appendChild(imgElem);
  const imgUrl = URL.createObjectURL(responseAsBlob);
  imgElem.src = imgUrl;
}

จากนั้นเพิ่มreadResponseAsBlobฟังก์ชันที่อ่านการตอบกลับเป็น Blob

function readResponseAsBlob(response) {
  return response.blob();
}

อัปเดตฟังก์ชัน fetchImage ด้วยโค้ดต่อไปนี้

function fetchImage() {
  fetch('examples/fetching.jpg')
    .then(validateResponse)
    .then(readResponseAsBlob)
    .then(showImage)
    .catch(logError);
}

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

คำอธิบาย

ในตัวอย่างนี้ ระบบกำลังดึงข้อมูลรูปภาพ examples/fetching.jpg เช่นเดียวกับการออกกำลังกายครั้งก่อน ระบบจะตรวจสอบการตอบกลับด้วย validateResponse จากนั้นจะอ่านการตอบกลับเป็น Blob (แทนที่จะเป็น JSON เหมือนในส่วนก่อนหน้า) ระบบจะสร้างองค์ประกอบรูปภาพและต่อท้ายในหน้าเว็บ และตั้งค่าแอตทริบิวต์ src ของรูปภาพเป็น URL ข้อมูลที่แสดง Blob

หมายเหตุ: ระบบจะใช้เมธอด createObjectURL() ของออบเจ็กต์ URL เพื่อสร้าง URL ของข้อมูลที่แสดง Blob โปรดทราบว่า คุณตั้งค่าแหล่งที่มาของรูปภาพเป็น Blob โดยตรงไม่ได้ ต้องแปลง Blob เป็น URL ข้อมูล

สำหรับข้อมูลเพิ่มเติม

ส่วนนี้เป็นความท้าทายที่ไม่บังคับ

อัปเดตฟังก์ชัน fetchText เพื่อ

  1. ดึงข้อมูล /examples/words.txt
  2. ตรวจสอบคำตอบด้วย validateResponse
  3. อ่านการตอบกลับเป็นข้อความ (คำแนะนำ: ดู Response.text())
  4. และแสดงข้อความในหน้าเว็บ

คุณสามารถใช้ฟังก์ชัน showText นี้เป็นตัวช่วยในการแสดงข้อความสุดท้ายได้

function showText(responseAsText) {
  const message = document.getElementById('message');
  message.textContent = responseAsText;
}

บันทึกสคริปต์แล้วรีเฟรชหน้าเว็บ คลิกดึงข้อมูลข้อความ หากติดตั้งใช้งาน fetchText อย่างถูกต้อง คุณควรเห็นข้อความที่เพิ่มในหน้า

หมายเหตุ: แม้ว่าการดึงข้อมูล HTML และการต่อท้ายโดยใช้แอตทริบิวต์ innerHTML อาจเป็นสิ่งที่น่าสนใจ แต่โปรดระมัดระวัง ซึ่งอาจทำให้เว็บไซต์เสี่ยงต่อการโจมตีแบบ Cross-Site Scripting

สำหรับข้อมูลเพิ่มเติม

โดยค่าเริ่มต้น ฟีเจอร์ Fetch จะใช้เมธอด GET ซึ่งจะดึงข้อมูลทรัพยากรที่เฉพาะเจาะจง แต่ Fetch ยังใช้วิธีการ HTTP อื่นๆ ได้ด้วย

ส่งคำขอ HEAD

แทนที่ฟังก์ชัน headRequest ด้วยโค้ดต่อไปนี้

function headRequest() {
  fetch('examples/words.txt', {
    method: 'HEAD'
  })
  .then(validateResponse)
  .then(readResponseAsText)
  .then(logResult)
  .catch(logError);
}

บันทึกสคริปต์แล้วรีเฟรชหน้าเว็บ คลิกคำขอ HEAD สังเกตว่าเนื้อหาข้อความที่บันทึกว่างเปล่า

คำอธิบาย

เมธอด fetch สามารถรับพารามิเตอร์ที่ 2 ซึ่งเป็นพารามิเตอร์ที่ไม่บังคับ init พารามิเตอร์นี้ช่วยให้กำหนดค่าคำขอเรียกข้อมูลได้ เช่น request method, โหมดแคช, ข้อมูลเข้าสู่ระบบ และอื่นๆ

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

ไม่บังคับ: ดูขนาดของทรัพยากร

มาดูส่วนหัวของการตอบกลับการดึงข้อมูลสำหรับ examples/words.txt เพื่อกำหนดขนาดของไฟล์กัน

อัปเดตฟังก์ชัน headRequest เพื่อบันทึกพร็อพเพอร์ตี้ content-length ของการตอบกลับ headers (คำใบ้: ดูเอกสารประกอบส่วนหัวและเมธอด get)

หลังจากอัปเดตโค้ดแล้ว ให้บันทึกไฟล์และรีเฟรชหน้าเว็บ คลิกคำขอ HEAD คอนโซลควรบันทึกขนาด (เป็นไบต์) ของ examples/words.txt

คำอธิบาย

ในตัวอย่างนี้ มีการใช้วิธี HEAD เพื่อขอขนาด (เป็นไบต์) ของทรัพยากร (แสดงในส่วนหัว content-length) โดยไม่ต้องโหลดทรัพยากรจริง ในทางปฏิบัติ คุณสามารถใช้ข้อมูลนี้เพื่อพิจารณาว่าควรขอทรัพยากรแบบเต็มหรือไม่ (หรือแม้กระทั่งวิธีขอ)

ไม่บังคับ: ค้นหาขนาดของ examples/words.txt โดยใช้วิธีอื่น และยืนยันว่าตรงกับค่าจากส่วนหัวของการตอบกลับ (คุณสามารถค้นหาวิธีดำเนินการนี้สำหรับระบบปฏิบัติการที่เฉพาะเจาะจงของคุณได้ ซึ่งเป็นโบนัสสำหรับการใช้บรรทัดคำสั่ง)

สำหรับข้อมูลเพิ่มเติม

นอกจากนี้ Fetch ยังส่งข้อมูลด้วยคำขอ POST ได้ด้วย

ตั้งค่าเซิร์ฟเวอร์ Echo

ในตัวอย่างนี้ คุณต้องเรียกใช้เซิร์ฟเวอร์ Echo จากไดเรกทอรี fetch-api-lab/app/ ให้เรียกใช้คำสั่งต่อไปนี้ (หากเซิร์ฟเวอร์ localhost:8081 บล็อกบรรทัดคำสั่ง ให้เปิดหน้าต่างหรือแท็บบรรทัดคำสั่งใหม่)

node echo-servers/cors-server.js

คำสั่งนี้จะเริ่มเซิร์ฟเวอร์อย่างง่ายที่ localhost:5000/ ซึ่งจะแสดงคำขอที่ส่งไปยังเซิร์ฟเวอร์นั้นกลับมา

คุณสิ้นสุดการใช้งานเซิร์ฟเวอร์นี้ได้ทุกเมื่อด้วย ctrl+c

ส่งคำขอ POST

แทนที่ฟังก์ชัน postRequest ด้วยโค้ดต่อไปนี้ (ตรวจสอบว่าคุณได้กำหนดฟังก์ชัน showText จากส่วนที่ 4 แล้ว หากยังไม่ได้ทำ)

function postRequest() {
  fetch('http://localhost:5000/', {
    method: 'POST',
    body: 'name=david&message=hello'
  })
    .then(validateResponse)
    .then(readResponseAsText)
    .then(showText)
    .catch(logError);
}

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

คำอธิบาย

หากต้องการส่งคำขอ POST ด้วยการเรียกข้อมูล เราจะใช้พารามิเตอร์ init เพื่อระบุเมธอด (คล้ายกับวิธีที่เราตั้งค่าเมธอด HEAD ในส่วนก่อนหน้า) นอกจากนี้ เรายังตั้งค่าเนื้อหาของคำขอในส่วนนี้ด้วย ซึ่งในกรณีนี้คือสตริงอย่างง่าย เนื้อหาคือข้อมูลที่เราต้องการส่ง

หมายเหตุ: ในเวอร์ชันที่ใช้งานจริง โปรดอย่าลืมเข้ารหัสข้อมูลผู้ใช้ที่มีความละเอียดอ่อนเสมอ

เมื่อส่งข้อมูลเป็นคำขอ POST ไปยัง localhost:5000/ ระบบจะส่งคำขอดังกล่าวกลับมาเป็นคำตอบ จากนั้นระบบจะตรวจสอบคำตอบด้วย validateResponse อ่านเป็นข้อความ และแสดงในหน้า

ในทางปฏิบัติ เซิร์ฟเวอร์นี้จะแสดง API ของบุคคลที่สาม

ไม่บังคับ: ใช้อินเทอร์เฟซ FormData

คุณสามารถใช้อินเทอร์เฟซ FormData เพื่อดึงข้อมูลจากแบบฟอร์มได้อย่างง่ายดาย

ในฟังก์ชัน postRequest ให้สร้างออบเจ็กต์ FormData ใหม่จากองค์ประกอบแบบฟอร์ม msg-form ดังนี้

const formData = new FormData(document.getElementById('msg-form'));

จากนั้นแทนที่ค่าของพารามิเตอร์ body ด้วยตัวแปร formData

บันทึกสคริปต์แล้วรีเฟรชหน้าเว็บ กรอกแบบฟอร์ม (ช่องชื่อและข้อความ) ในหน้าเว็บ แล้วคลิกคำขอ POST สังเกตเนื้อหาแบบฟอร์มที่แสดงในหน้า

คำอธิบาย

เครื่องมือสร้าง FormData สามารถรับ form HTML และสร้างออบเจ็กต์ FormData ได้ ระบบจะป้อนข้อมูลคีย์และค่าของแบบฟอร์มลงในออบเจ็กต์นี้

สำหรับข้อมูลเพิ่มเติม

เริ่มเซิร์ฟเวอร์ก้องที่ไม่ใช่ CORS

หยุดเซิร์ฟเวอร์ Echo ก่อนหน้า (โดยกด ctrl+c จากบรรทัดคำสั่ง) แล้วเริ่มเซิร์ฟเวอร์ Echo ใหม่จากไดเรกทอรี fetch-lab-api/app/ โดยเรียกใช้คำสั่งต่อไปนี้

node echo-servers/no-cors-server.js

คำสั่งนี้จะตั้งค่าเซิร์ฟเวอร์ Echo แบบง่ายอีกเซิร์ฟเวอร์หนึ่ง ซึ่งคราวนี้อยู่ที่ localhost:5001/ อย่างไรก็ตาม เซิร์ฟเวอร์นี้ไม่ได้กำหนดค่าให้ยอมรับคำขอข้ามโดเมน

ดึงข้อมูลจากเซิร์ฟเวอร์ใหม่

ตอนนี้เซิร์ฟเวอร์ใหม่ทำงานที่ localhost:5001/ แล้ว เราจึงส่งคำขอเรียกข้อมูลไปยังเซิร์ฟเวอร์นั้นได้

อัปเดตฟังก์ชัน postRequest เพื่อดึงข้อมูลจาก localhost:5001/ แทน localhost:5000/ หลังจากอัปเดตโค้ดแล้ว ให้บันทึกไฟล์ รีเฟรชหน้าเว็บ แล้วคลิกคำขอ POST

คุณควรได้รับข้อผิดพลาดในคอนโซลที่ระบุว่าคำขอข้ามต้นทางถูกบล็อกเนื่องจากไม่มีส่วนหัว Access-Control-Allow-Origin ของ CORS

อัปเดต fetch ในฟังก์ชัน postRequest ด้วยโค้ดต่อไปนี้ ซึ่งใช้โหมด no-cors (ตามที่บันทึกข้อผิดพลาดแนะนำ) และนำการเรียกใช้ validateResponse และ readResponseAsText ออก (ดูคำอธิบายด้านล่าง)

function postRequest() {
  const formData = new FormData(document.getElementById('msg-form'));
  fetch('http://localhost:5001/', {
    method: 'POST',
    body: formData,
    mode: 'no-cors'
  })
    .then(logResult)
    .catch(logError);
}

บันทึกสคริปต์แล้วรีเฟรชหน้าเว็บ จากนั้นกรอกแบบฟอร์มข้อความแล้วคลิกคำขอ POST

สังเกตออบเจ็กต์การตอบกลับที่บันทึกไว้ในคอนโซล

คำอธิบาย

Fetch (และ XMLHttpRequest) เป็นไปตามนโยบายแหล่งที่มาเดียวกัน ซึ่งหมายความว่าเบราว์เซอร์จะจำกัดคำขอ HTTP แบบข้ามแหล่งที่มาจากภายในสคริปต์ คำขอข้ามแหล่งที่มาจะเกิดขึ้นเมื่อโดเมนหนึ่ง (เช่น http://foo.com/) ขอทรัพยากรจากโดเมนอื่น (เช่น http://bar.com/)

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

เนื่องจากเซิร์ฟเวอร์ของแอปมีหมายเลขพอร์ตที่แตกต่างจากเซิร์ฟเวอร์ Echo ทั้ง 2 เซิร์ฟเวอร์ คำขอที่ส่งไปยังเซิร์ฟเวอร์ Echo เซิร์ฟเวอร์ใดเซิร์ฟเวอร์หนึ่งจึงถือว่าเป็นคำขอข้ามต้นทาง อย่างไรก็ตาม เซิร์ฟเวอร์ Echo แรกที่ทำงานบน localhost:5000/ ได้รับการกำหนดค่าให้รองรับ CORS (คุณสามารถเปิด echo-servers/cors-server.js และตรวจสอบการกำหนดค่าได้) เซิร์ฟเวอร์ Echo ใหม่ที่ทำงานบน localhost:5001/ ไม่ได้ (ซึ่งเป็นสาเหตุที่เราได้รับข้อผิดพลาด)

การใช้ mode: no-cors จะช่วยให้ดึงข้อมูลการตอบกลับแบบทึบแสงได้ ซึ่งจะช่วยให้เราได้รับการตอบกลับ แต่จะป้องกันการเข้าถึงการตอบกลับด้วย JavaScript (ซึ่งเป็นเหตุผลที่เราใช้ validateResponse, readResponseAsText หรือ showResponse ไม่ได้) การตอบกลับยังคงใช้ได้กับ API อื่นๆ หรือแคชโดย Service Worker

แก้ไขส่วนหัวของคำขอ

นอกจากนี้ Fetch ยังรองรับการแก้ไขส่วนหัวของคำขอด้วย หยุดเซิร์ฟเวอร์ก้อง localhost:5001 (ไม่มี CORS) และรีสตาร์ทเซิร์ฟเวอร์ก้อง localhost:5000 (CORS) จากส่วนที่ 6:

node echo-servers/cors-server.js

กู้คืนฟังก์ชัน postRequest เวอร์ชันก่อนหน้าซึ่งดึงข้อมูลจาก localhost:5000/

function postRequest() {
  const formData = new FormData(document.getElementById('msg-form'));
  fetch('http://localhost:5000/', {
    method: 'POST',
    body: formData
  })
    .then(validateResponse)
    .then(readResponseAsText)
    .then(showText)
    .catch(logError);
}

ตอนนี้ให้ใช้อินเทอร์เฟซส่วนหัวเพื่อสร้างออบเจ็กต์ส่วนหัวภายในฟังก์ชัน postRequest ที่ชื่อ messageHeaders โดยมีส่วนหัว Content-Type เท่ากับ application/json

จากนั้นตั้งค่าพร็อพเพอร์ตี้ headers ของออบเจ็กต์ init ให้เป็นตัวแปร messageHeaders

อัปเดตพร็อพเพอร์ตี้ body ให้เป็นออบเจ็กต์ JSON ที่แปลงเป็นสตริงแล้ว เช่น

JSON.stringify({ lab: 'fetch', status: 'fun' })

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

สังเกตว่าคำขอที่ส่งกลับมาตอนนี้มี Content-Type เป็น application/json (แทนที่จะเป็น multipart/form-data เหมือนก่อนหน้านี้)

ตอนนี้ให้เพิ่มContent-Lengthส่วนหัวที่กำหนดเองmessageHeadersลงในออบเจ็กต์ messageHeaders แล้วกำหนดขนาดตามต้องการให้กับคำขอ

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

คำอธิบาย

อินเทอร์เฟซส่วนหัวช่วยให้สร้างและแก้ไขออบเจ็กต์ส่วนหัวได้ ส่วนหัวบางรายการ เช่น Content-Type สามารถแก้ไขได้โดยการดึงข้อมูล ส่วนอื่นๆ เช่น Content-Length จะได้รับการป้องกันและแก้ไขไม่ได้ (เพื่อเหตุผลด้านความปลอดภัย)

ตั้งค่าส่วนหัวคำขอที่กำหนดเอง

Fetch รองรับการตั้งค่าส่วนหัวที่กำหนดเอง

นำส่วนหัว Content-Length ออกจากออบเจ็กต์ messageHeaders ในฟังก์ชัน postRequest เพิ่มส่วนหัวที่กำหนดเอง X-Custom โดยมีค่าใดก็ได้ (เช่น 'X-CUSTOM': 'hello world')

บันทึกสคริปต์ รีเฟรชหน้าเว็บ แล้วคลิกคำขอ POST

คุณควรเห็นว่าคำขอที่ส่งกลับมีพร็อพเพอร์ตี้ X-Custom ที่คุณเพิ่ม

ตอนนี้ให้เพิ่มY-Customส่วนหัวไปยังออบเจ็กต์ส่วนหัว บันทึกสคริปต์ รีเฟรชหน้าเว็บ แล้วคลิกคำขอ POST

คุณควรได้รับข้อผิดพลาดที่คล้ายกับข้อความต่อไปนี้ในคอนโซล

Fetch API cannot load http://localhost:5000/. Request header field y-custom is not allowed by Access-Control-Allow-Headers in preflight response.

คำอธิบาย

เช่นเดียวกับคำขอข้ามแหล่งที่มา เซิร์ฟเวอร์ที่ขอทรัพยากรต้องรองรับส่วนหัวที่กำหนดเอง ในตัวอย่างนี้ เราได้กำหนดค่าเซิร์ฟเวอร์ Echo ให้ยอมรับส่วนหัว X-Custom แต่ไม่ยอมรับส่วนหัว Y-Custom (คุณสามารถเปิด echo-servers/cors-server.js แล้วมองหา Access-Control-Allow-Headers เพื่อดูด้วยตนเอง) ทุกครั้งที่มีการตั้งค่าส่วนหัวที่กำหนดเอง เบราว์เซอร์จะทำการตรวจสอบก่อนส่งคำขอ ซึ่งหมายความว่าเบราว์เซอร์จะส่งคำขอ OPTIONS ไปยังเซิร์ฟเวอร์ก่อนเพื่อพิจารณาว่าเซิร์ฟเวอร์อนุญาตให้ใช้เมธอดและส่วนหัว HTTP ใด หากเซิร์ฟเวอร์ได้รับการกำหนดค่าให้ยอมรับเมธอดและส่วนหัวของคำขอเดิม ระบบจะส่งคำขอ แต่หากไม่เป็นเช่นนั้น ระบบจะแสดงข้อผิดพลาด

สำหรับข้อมูลเพิ่มเติม

รหัสโซลูชัน

หากต้องการรับสำเนาของโค้ดที่ใช้งานได้ ให้ไปที่โฟลเดอร์โซลูชัน

ตอนนี้คุณก็ทราบวิธีใช้ Fetch API แล้ว

แหล่งข้อมูล

หากต้องการดู Codelab ทั้งหมดในหลักสูตรการฝึกอบรม PWA โปรดดู Codelab ต้อนรับสำหรับหลักสูตร