วิธีรับมือ Dev มือใหม่ เวลาใช้ Axios
*หมายเหตุ ตอนนี้พยายามเขียนบทความให้มากขึ้น เลยพยายามเขียนให้เร็ว อาจจะมีการตกล่นอะไร โปรดแจ้งได้เลยนะครับผมจะแก้ไขให้เร็วที่สุด
ปัญหาจากมือใหม่เวลาใช้ Axios
ผมคิดว่าสำหรับผู้พัฒนาเว็บไซต์ คงไม่มีใครไม่รู้จัก Axios [axios-http.com] วิธีใช้ก็แสนง่ายตามตัวอย่างใน Document
สรุปสั้นๆ Axios จะ ส่งค่าทุกอย่างในรูปแบบ Promise ถ้า Status 200 ก็ลง Then นอกเหนือจากนั้น ก็ลง Catch
ซึ่งก็ดูปกติดี… แต่เดี่ยวก่อน…
ปัญหาแรก
คือ Axios เอานอกเหนือจาก Status 200 ลง Catch หมดนี่ล่ะครับ
ซึ่ง Backend ที่ดีจะไม่ได้ใช้แค่ Status 200 ในการส่งค่ากลับมายัง Frontend
และใน Error ก็มี ข้อมูล Response ส่งกลับมาเตือนผู้ใช้ด้วยด้วย อาทิเช่น บอกว่า คุณใส่ password ผิด , email นี้มีคนใช้แล้ว , ไม่มีข้อมูลที่คุณค้นหา
เราเรียกว่า Error Response มันคือค่าที่ส่งกลับมาปกติ แต่มันอยู่ใน error.response แทน
ขอยืมรูปตัวอย่างจากบทความคุณ Brian Mulloy จะเห็นได้ว่าต่อให้ 200 , 400 ก็มี Response ที่ Backend พยายามส่งมาอยู่
แล้วผู้พัฒนามือใหม่ชอบลืมจัดการตรงนี้ โดยไม่ได้สนใจว่า Error นั้นมี Response หรือไม่ จากมี Response กลายเป็น Error แบบไม่มี Response ทั้งหมด ย้ำรูปนี้อีกที Error ทั้งหมดก็คือ Error
ปัญหาที่ 2
ที่จะชอบเกิดกับมือใหม่ คือ Try-Catch Hell แปลตามตัว ขุมนรก Try-Catch โดยการเอา Logic ยาวๆ ไปใส่ไว้ใน Try (ก็ตัวอย่างทางการแนะนำมา) มีปัญหาอะไรก็ Try Catch วนไป ดังตัวอย่าง
try {
const response = await Axios.get(url); // ... โค๊ด Logic ซัก 100 บรรทัด try { // มีปัญหาซักอย่าง เลยต้อง Try-Catch
const data = helperFunction(response); try { // มีปัญหาซักอย่างก็ Try-Catch Hell วนไป
showData(data);
} catch (error: any) {
//...
} } catch (error: any) {
// ...
}} catch (error: any) {
console.log(error);
}
พอเกิด Catch จาก Axios ฝั่งผู้พัฒนามือใหม่ก็ชอบ Try Catch แบบเหมาคลุม Logic แบบยาวๆมากๆ แล้วก็ชอบทำ Try-Catch ซ้อนไปเรื่อยๆด้วย
ซึ่ง Try Catch เวลามันพัง มันจะย้อนโค๊ดกลับไปทีละบรรทัด เหมือนย้อนเวลา แล้วยิ่งบรรทัดเยอะเท่าไหร่ก็จะยิ่งหน่วงมากๆครับ แบบ Response เร็ว แต่ Error ดันรอเป็น 10 วินาทีก็มีกันเลยทีเดียว
วิธีแก้ปัญหา
ผมเลยต้องเขียน Function Helper ไว้ให้ใช้ ซึ่งหน้าตาจะประมาณนี้ครับ โดยดึงแนวความคิดของ Golang มา ที่จะส่งค่า Response ไปพร้อมกับ Error กลับไป
// โยน Axios Promise เข้ามาได้เลย
export const helperAxiosAPI = async (promiseAPI: any) => {
return promiseAPI.then(
(response: any) => {// ถ้าผ่าน ก็ส่ง response กลับไป ไม่ส่ง response.data เพื่อให้ dev เช็ค status กับ header ได้
return { res: response, err: null };
}
).catch((error: any) => {// ถ้าไม่ผ่านแต่มี response.data ก็นับว่าเป็น response อยู่แล้วค่อยเอา response ไปเช็คข้างในอีกที
if (error.response && typeof error.response.data == 'object') { // ไม่ส่ง response.data เพื่อให้ dev เช็ค status กับ header ได้
return { res: error.response, err: error };
}
// อันนี้ Error เพียวๆเลย Res ก็ไม่มี
return { res: null, err: error };
});
};
// พอดีลองเขียนสดน่าจะประมาณนี้ พอดีตัดส่วนที่ช่วยเหลือผู้พัฒนา จากตัวจริงไปพอสมควร เหลือแต่ส่วนหลักๆ ยังไม่ลอง Compile ถ้าผิดยังไงแจ้งได้ขออภัยครับ
โดยวิธีใช้
// โยน Axios ลงไปได้เลย ไม่ต้อง await 2 รอบ
const { res, err } = await helperAxiosAPI(Axios.get('url'));// ถ้า Error แล้ว ไม่มี Response ก็ให้แสดง Error
if (err && !res) {
console.log('err', err);
return null
}// ถ้าไม่มีข้อมูลกลับมาเลย แสดงผลอะไรซักอย่าง (มีหรือไม่มีก็ได้แล้วแต่ Logic)
if(!res){
console.log("Not Have Data");
return null
}// ข้อมูลที่ต้องการ
console.log("Success:",res);
หรือคนที่ชอบ throw คือโยน Error ให้ Middleware จัดการ ก็
const { res, err } = await helperAxiosAPI(Axios.get('url'));// ถ้า Error แล้ว ไม่มี Response ก็ให้แสดง Error
if (err) {
throw err
}// หรือจะ throw เฉพาะ Error จริงๆ
if (err && !res) {
throw err
}// ที่เหลือ เหมือนเดิม
if(!res){
console.log("Not Have Data");
return null
}// ข้อมูลที่ต้องการ
console.log("Success:",res);
เท่านี้ Axios ก็จะอ่านง่ายและดูดีขึ้นทันตาเห็นเลย ไม่มี Try-Catch Hell , Promise Hell , If-Else Hell ตรงไปตรงมาแบบ Golang
เพิ่มเติม อีกวิธีที่ใครคิดว่า Helper เกะกะลูกตา
มีคนแนะนำการใช้ interceptors เข้าช่วยใน axios เองจะได้ไม่มี function ครอบ
axios.interceptors.response.use( (res) =>{
return {res: res , err: null};
}, (error)=> {
if(error.response && typeof error.response.data == 'object'){// ไม่ส่ง response.data เพื่อให้ dev เช็ค status กับ header ได้
return { res: error.response, err: error };
}
return {res: null , err: error}
});// เวลาใช้ก็เรียกได้เลย
const { res, err } = await Axios.get('url');
สรุป ทำไปทำไม ทำแล้วได้อะไร
เนื่องจากมือใหม่ หรือ Junior มักจะทำงานแบบ Best Case Only ทำก่อนแก้ทีหลัง พอทำๆไป โค๊ดก็สุม คนที่มาทำต่อก็เข้าใจยาก โดยทำเพื่อ
- เบาแรง Dev ลดโค๊ดที่ใช้ซ้ำซาก จะได้น้อยลง จะได้เอาเวลาไปสนใจเฉพาะ Business Logic ต่างๆ แค่ส่วนนี้ก็เยอะแล้ว และถ้าไม่ใช้ก็ดีกว่าไม่มีเดฟไม่ต้องเอา res ที่ทำมาใช้ต่อก็ได้
- ลดการทำ Hell รูปแบบต่างๆ พอปล่อยให้ Dev มือใหม่จัดการเอง คือเละครับ เขียนแบบไม่ติด Hell ไม่เป็น กว่าจะเป็นต้องเจ็บตัวมาเยอะ ผมเลยดักไว้ก่อนไปเลย
- จากข้างต้นทำให้อ่านง่ายขึ้น เผื่อคนที่มาทำต่อจะได้ไม่ต้องมาปวดหัว
- ทำ Function เพื่ออำนวยความสะดวกเพิ่มทีหลังได้ ที่จริงๆ ตรง helperAxiosAPI ไม่มั่นใจ ไม่ได้ compile ลอง เพราะตัดทอนส่วนที่ช่วยต่างๆ เช่นเช็ค Validate Request , Pagination , Noti , Log ต่างๆ ที่ทำไว้เหลือเฉพาะส่วนสำคัญครับ
ถ้าชอบยังไงฝากแชร์ฝากแสดงความคิดเห็นหน่อยนะครับ
หรือฝากถูกใจเพจเฟสบุ๊คบริษัทต่างโลก อินเตอร์แอคทีฟ จำกัด ได้นะครับ
ขอบคุณครับ
เพิ่มเติม สำหรับคนสงสัยเรื่อง Try-Catch
เมื่อ Try-Catch ซ้อนกันหลายๆชั้น ถ้าชั้นที่ลึกสุด ดักได้แล้ว ชั้นบนๆ ก็ควรจะไม่ Catch ใช่หรือเปล่า จะช้าได้ไง
ใช่ครับ แต่ทั้งนี้ บางคน Catch แล้ว Throw Error ใหม่ขึ้นไปข้างบนเรื่อยๆ พออยู่บนสุด จะเข้า Middleware ที่จัดการ Error ให้สวยๆ ตัวอย่าง
catch(error: any){ throw new Error(“Not Have Data”);}
อัจฉริยะสุดๆ ทีนี้ย้อนกันมันเลยครับ ต้องมาดักอีกว่า Error จริง หรือ Error ที่ออกแบบเอง แล้วเราก็ไม่แน่ใจว่า Dev จะครอบ Try Catch สั้นสุดไหม ครอบยาวๆ ก็หน่วงยาวๆครับ
เพิ่มเติม 2
มีท่านนึงถามในกระทู้ มีโจทย์ ถ้าต้องทำ act1–3 จะสำเร็จหรือไม่ค่อยทำ act4 ตาม แบบข้างล่าง
try {
await act1()
await act2()
await act3()
} catch (e) {}
await act4()
แล้วทำเป็น เช็ค error โดยใช้ If แบบนี้มันไม่เป็น If-Else Hell หรือครับ
let err1 = await act1()
if (!err1) {
let err2 = await act2()
if (!err2) {
await act3()
}
}
await act4()
ซึ่งจริงๆสามารถแก้ได้ครับเป็น
let err1 = await act1()
if err1 return act4()let err2 = await act2()
if err2 return act4()let err3 = await act3()await act4()
สังเกตว่าอ่านง่ายกว่าเดิมเยอะเลยครับ หรือถ้าไม่ติดเรื่องต้องเช็ค Error ก็ปล่อยผ่านก็ได้ครับ จะได้ Error รัวๆ อยากเช็คก็เช็คทีเดียว
let err1 = await act1()
let err2 = await act2()
let err3 = await act3()let err4 = await act4()
ขอบคุณครับ