ปัจจุบันการส่ง XMLHttpRequest
ไปยัง Backend API นั้นเป็นเรื่องพื้นฐานที่มีการใช้งานกันแทบทุกเว็บแอพ แต่ด้วยธรรมชาติของ Asynchronous ใน JavaScript นั้นทำให้การแสดงผลลัพธ์ของ Response อาจไม่เป็นไปตามลับดับในการส่ง Request เสมอไป แล้วถ้าการแสดงผลลัพธ์ของ Response ไม่ได้เป็นไปตามลำดับที่ควรจะเป็นหล่ะ จะเกิดปัญหาอย่างไร
เคสตัวอย่างปัญหาเช่น ในการทำระบบแสดงสินค้าตามหมวดหมู่นั้น ในขั้นต้นผู้ใช้อาจจะคลิกเลือกแสดงสินค้าในหมวด A แต่เกิดเปลี่ยนใจกระทันหัน ไปคลิกเลือกแสดงสินค้าในหมวด B แทน ในขณะที่ผลลัพธ์ของหมวด A ยังไม่มีการ Response กลับมา
สิ่งที่นักพัฒนาอาจจะเข้าใจไปเองคือ เมื่อมีการส่ง Request ครั้งใหม่เกิดขึ้น axios
ก็จะไม่สนใจ Response ของ Request ก่อนหน้านั้นอีก แต่ในความเป็นจริงแล้ว Response ทุกตัวยังคงถูกจัดการตามลำดับของการ Response อยู่ดี (ไม่ใช่ลำดับของการส่ง Request) แสดงว่าหากโชคไม่ดี Response ของสินค้าของหมวด B ดันกลับมาถึงซะก่อน หน้าจอแสดงผลของผู้ใช้จะแสดงผลสินค้าในหมวด A (เพราะดันกลับมาถึงทีหลัง จึงไปแทนที่ผลลัพธ์ที่กลับมาถึงก่อน) ทั้งๆ ที่ผู้ใช้เปลี่ยนใจ และได้คลิกแสดงสินค้าหมวด B ไปแล้ว
ดังนั้นสิ่งที่นักพัฒนาควรทำก็คือให้แอพของเราเพิกเฉยที่จะไปจัดการกับ Response ของ Request ก่อนหน้าทันที ที่มีการส่ง Request ใหม่
ในตัวอย่างโค้ดของบทความนี้ เราจะเลือกใช้ Vue.js เนื่องจากเป็นไลบรารี่ยอดนิยมและใช้งานง่าย โดยจะใช้ axios
เป็นตัวส่ง XMLHttpRequest
และจัดการกับ Response ครับ
axios
คือไลบรารี่ที่ใช้ในการส่ง XMLHttpRequest
สำหรับการพัฒนาแอพฝั่ง Client และยังสามารถใช้ส่ง HTTP Request สำหรับการพัฒนาแอพด้วย Node.js ด้วยเช่นกันครับ
Path
|-package.json
+-src
| |-main.js
| |-App.vue
axios
พิมพ์คำสั่งที่ Terminal
npm install axios --save
axios
เพื่อความง่ายในการสาธิต เราจะเพียงแค่ทดลองส่ง Request เรียงลำดับกันจำนวนหนึ่งเพื่อให้เห็นปัญหาว่าการ Response ของผลลัพธ์นั้นอาจจะไม่เรียงลำดับตามลำดับของการส่ง Request เสมอไป โดยเราจะใช้ Backend API ของ https://jsonplaceholder.typicode.com
และใช้ Endpoint https://jsonplaceholder.typicode.com/users ในการเขียนโค้ด
ไฟล์ src/App.vue
<button @click="makeMultipleRequests()">Make Multiple Requests</button>
import axios from 'axios'
const uri = 'https://jsonplaceholder.typicode.com/users'
export default {
methods: {
async makeRequest(id) {
const { data } = await axios.get(`${ uri }/${ id }`)
console.log(`ID: ${ data.id } Name: ${ data.name }`)
},
makeMultipleRequests() {
const userIds = [1, 2, 3]
// loop เพื่อส่ง request แต่ละ id
userIds.forEach(id => {
this.makeRequest(id)
})
}
}
}
จากผลลัพธ์
console.log()
ของตัวอย่างข้างต้นจะเห็นว่าลำดับการกลับมาของ Response นั้น อาจจะไม่ได้เรียงลำดับตามการส่ง Request เสมอไปครับ
cancelToken
สิ่งที่นักพัฒนาควรทำคือการทำให้ axios
เพิกเฉยกับ Response ของ Request ก่อนหน้าทั้งหมด (Response ยังคงกลับมาตามปกติ เพียงแต่ถูกเพิกเฉยไม่นำผลลัพธ์มาแสดง) ซึ่ง axios
ก็มีตัวช่วยเรื่องนี้ให้เราพร้อมแล้ว โดยใช้หลักการว่าเราสามารถใช้ Object CancelToken
ในการสร้าง cancelToken
เพื่อแนบไปกับ Request ในการส่งแต่ละครั้ง เพื่อให้ axios
สามารถอ้างอิง Request แต่ละตัวได้อย่างถูกต้อง และทุกครั้งที่จะส่ง Request ก็จะตรวจสอบก่อนว่ามี Request ก่อนหน้ารึเปล่า ถ้ามีก็ยกเลิกตัวก่อนหน้า เพราะ axios
รู้จักแล้วว่าตัวไหนคือตัวไหน
ในขั้นตอนนี้เราจะแก้ไขโค้ดของเราเพียงแค่ Import Object CancelToken
มาใช้ และปรับปรุง Method makeRequest()
ส่วนอื่นๆ เหมือนเดิมทั้งหมดครับ
ไฟล์ src/App.vue
import axios, { CancelToken } from 'axios'
export default {
methods: {
async makeRequest(id) {
// เพิกเฉย request ก่อนหน้า
if (this.source) {
this.source.cancel()
}
// สร้าง source ใหม่
this.source = CancelToken.source()
// รับค่า token จาก source
const cancelToken = this.source.token
// ส่ง request โดยอ้างอิง cancelToken
const { data } = await axios.get(`${ uri }/${ id }`, { cancelToken })
console.log(`ID: ${data.id} Name: ${data.name}`)
}
}
}
จากผลลัพธ์
console.log()
ของตัวอย่างข้างต้นจะเห็นว่า Request ก่อนหน้าทั้งหมดถูกเพิกเฉยแอพจึงแสดงผลลัพธ์ล่าสุดเท่านั้นครับ
ไฟล์ src/App.vue
<button @click="makeMultipleRequests()">Make Multiple Requests</button>
import axios, { CancelToken } from 'axios'
const uri = 'https://jsonplaceholder.typicode.com/users'
export default {
methods: {
async makeRequest(id) {
// เพิกเฉย request ก่อนหน้า
if (this.source) {
this.source.cancel()
}
// สร้าง source ใหม่
this.source = CancelToken.source()
// รับค่า token จาก source
const cancelToken = this.source.token
// ส่ง request โดยอ้างอิง cancelToken
const { data } = await axios.get(`${ uri }/${ id }`, { cancelToken })
console.log(`ID: ${data.id} Name: ${data.name}`)
},
makeMultipleRequests() {
const userIds = [1, 2, 3]
// loop เพื่อส่ง request แต่ละ id
userIds.forEach(id => {
this.makeRequest(id)
})
}
}
}