ไม่ว่าเราจะเก่งการเขียนโปรแกรมแค่ไหน บางครั้งสคริปต์ของเราก็มีข้อผิดพลาด สิ่งเหล่านี้อาจเกิดขึ้นเนื่องจากความผิดพลาดของเรา การป้อนข้อมูลโดยไม่คาดคิดจากผู้ใช้ การตอบกลับของเซิร์ฟเวอร์ที่ผิดพลาด และด้วยเหตุผลอื่นๆ อีกนับพันประการ
โดยปกติแล้วสคริปต์จะ "ตาย" (หยุดทันที) ในกรณีที่เกิดข้อผิดพลาด โดยจะพิมพ์ไปยังคอนโซล
แต่มีโครงสร้างไวยากรณ์ try...catch
ที่ช่วยให้เรา "จับ" ข้อผิดพลาดได้ ดังนั้นสคริปต์จึงสามารถทำสิ่งที่สมเหตุสมผลมากกว่า แทนที่จะตาย
โครงสร้าง try...catch
มีสองช่วงตึกหลัก: try
แล้ว catch
:
พยายาม { // รหัส... } จับ (ผิดพลาด) { // การจัดการข้อผิดพลาด -
มันทำงานเช่นนี้:
ขั้นแรก โค้ดใน try {...}
จะถูกดำเนินการ
หากไม่มีข้อผิดพลาด catch (err)
จะถูกละเว้น: การดำเนินการถึงจุดสิ้นสุดของ try
และดำเนินต่อไปโดยข้าม catch
หากมีข้อผิดพลาดเกิดขึ้น การดำเนินการ try
จะหยุดลง และการควบคุมจะไหลไปที่จุดเริ่มต้นของ catch (err)
ตัวแปรข้อ err
(เราสามารถใช้ชื่อใดก็ได้) จะมีวัตถุข้อผิดพลาดพร้อมรายละเอียดเกี่ยวกับสิ่งที่เกิดขึ้น
ดังนั้น ข้อผิดพลาดภายในบล็อก try {...}
ไม่ได้ทำให้สคริปต์เสียหาย – เรามีโอกาสที่จะจัดการกับมันด้วย catch
ลองดูตัวอย่างบางส่วน
ตัวอย่างที่ไม่มีข้อผิดพลาด: แสดง alert
(1)
และ (2)
:
พยายาม { alert('เริ่มลองรัน'); // (1) <-- // ...ไม่มีข้อผิดพลาดที่นี่ alert('สิ้นสุดการลองรัน'); // (2) <-- } จับ (ผิดพลาด) { alert('ระบบจะละเว้นการตรวจจับ เนื่องจากไม่มีข้อผิดพลาด'); // (3) -
ตัวอย่างที่มีข้อผิดพลาด: แสดง (1)
และ (3)
:
พยายาม { alert('เริ่มลองรัน'); // (1) <-- ลาลาลา; // ผิดพลาด ไม่ได้กำหนดตัวแปร! alert('สิ้นสุดการลอง (ไม่ถึง)'); // (2) } จับ (ผิดพลาด) { alert(`เกิดข้อผิดพลาด!`); // (3) <-- -
try...catch
ใช้ได้กับข้อผิดพลาดรันไทม์เท่านั้น
หากต้องการ try...catch
to work โค้ดจะต้องสามารถรันได้ กล่าวอีกนัยหนึ่ง มันควรจะเป็น JavaScript ที่ถูกต้อง
มันจะไม่ทำงานหากโค้ดผิดทางไวยากรณ์ เช่น มีเครื่องหมายปีกกาที่ไม่ตรงกัน:
พยายาม { - } จับ (ผิดพลาด) { alert("ระบบไม่เข้าใจรหัสนี้ มันไม่ถูกต้อง"); -
เอ็นจิ้น JavaScript จะอ่านโค้ดก่อนแล้วจึงรัน ข้อผิดพลาดที่เกิดขึ้นในช่วงการอ่านเรียกว่าข้อผิดพลาด "เวลาแยกวิเคราะห์" และไม่สามารถกู้คืนได้ (จากภายในโค้ดนั้น) นั่นเป็นเพราะเครื่องยนต์ไม่สามารถเข้าใจรหัสได้
ดังนั้น try...catch
สามารถจัดการได้เฉพาะข้อผิดพลาดที่เกิดขึ้นในโค้ดที่ถูกต้องเท่านั้น ข้อผิดพลาดดังกล่าวเรียกว่า "ข้อผิดพลาดรันไทม์" หรือบางครั้งเรียกว่า "ข้อยกเว้น"
try...catch
ทำงานพร้อมกัน
หากมีข้อยกเว้นเกิดขึ้นในโค้ด "ตามกำหนดเวลา" เช่นใน setTimeout
ให้ try...catch
จะไม่จับ:
พยายาม { setTimeout (ฟังก์ชัน () { ไม่มีตัวแปรดังกล่าว; // สคริปต์จะตายที่นี่ }, 1,000); } จับ (ผิดพลาด) { alert( "ใช้งานไม่ได้" ); -
นั่นเป็นเพราะว่าฟังก์ชันนั้นถูกดำเนินการในภายหลัง เมื่อเครื่องยนต์ออกจากโครงสร้าง try...catch
แล้ว
หากต้องการตรวจจับข้อยกเว้นภายในฟังก์ชันที่กำหนดเวลาไว้ try...catch
ต้องอยู่ภายในฟังก์ชันนั้น:
setTimeout (ฟังก์ชัน () { พยายาม { ไม่มีตัวแปรดังกล่าว; // try...catch จัดการข้อผิดพลาด! } จับ { alert( "พบข้อผิดพลาดที่นี่!" ); - }, 1,000);
เมื่อมีข้อผิดพลาดเกิดขึ้น JavaScript จะสร้างออบเจ็กต์ที่มีรายละเอียดเกี่ยวกับข้อผิดพลาดนั้น วัตถุจะถูกส่งผ่านเป็นอาร์กิวเมนต์เพื่อ catch
:
พยายาม { - } catch (ผิดพลาด) { // <-- "วัตถุข้อผิดพลาด" สามารถใช้คำอื่นแทนข้อผิดพลาดได้ - -
สำหรับข้อผิดพลาดในตัวทั้งหมด ออบเจ็กต์ข้อผิดพลาดมีคุณสมบัติหลักสองประการ:
name
ชื่อข้อผิดพลาด ตัวอย่างเช่น สำหรับตัวแปรที่ไม่ได้กำหนดนั่นคือ "ReferenceError"
message
ข้อความเกี่ยวกับรายละเอียดข้อผิดพลาด
มีคุณสมบัติที่ไม่เป็นมาตรฐานอื่นๆ ที่มีอยู่ในสภาพแวดล้อมส่วนใหญ่ หนึ่งในสิ่งที่ใช้กันอย่างแพร่หลายและได้รับการสนับสนุนมากที่สุดคือ:
stack
Call Stack ปัจจุบัน: สตริงที่มีข้อมูลเกี่ยวกับลำดับการโทรที่ซ้อนกันซึ่งนำไปสู่ข้อผิดพลาด ใช้เพื่อวัตถุประสงค์ในการดีบัก
ตัวอย่างเช่น:
พยายาม { ลาลาลา; // ผิดพลาด ไม่ได้กำหนดตัวแปร! } จับ (ผิดพลาด) { alert(err.name); // ข้อผิดพลาดในการอ้างอิง การแจ้งเตือน (ข้อผิดพลาดข้อความ); // lalala ไม่ได้ถูกกำหนดไว้ การแจ้งเตือน (err.stack); // ReferenceError: lalala ไม่ได้ถูกกำหนดไว้ที่ (...call stack) // สามารถแสดงข้อผิดพลาดโดยรวมได้ // ข้อผิดพลาดถูกแปลงเป็นสตริงเป็น "ชื่อ: ข้อความ" แจ้งเตือน(ผิดพลาด); // ReferenceError: ไม่ได้กำหนด lalala -
นอกจากนี้ล่าสุด
นี่เป็นส่วนเพิ่มเติมล่าสุดของภาษา เบราว์เซอร์รุ่นเก่าอาจต้องใช้โพลีฟิล
หากเราไม่ต้องการรายละเอียดข้อผิดพลาด catch
อาจละเว้น:
พยายาม { - } catch { // <-- ไม่มี (ผิดพลาด) - -
มาสำรวจกรณีการใช้งานจริงของ try...catch
ดังที่เราทราบแล้วว่า JavaScript รองรับเมธอด JSON.parse(str) เพื่ออ่านค่าที่เข้ารหัสด้วย JSON
โดยปกติจะใช้เพื่อถอดรหัสข้อมูลที่ได้รับผ่านเครือข่าย จากเซิร์ฟเวอร์หรือแหล่งอื่น
เราได้รับมันและเรียก JSON.parse
ดังนี้:
ให้ json = '{"name":John", "age": 30}'; //ข้อมูลจากเซิร์ฟเวอร์ ให้ผู้ใช้ = JSON.parse(json); // แปลงการแสดงข้อความเป็นวัตถุ JS // ตอนนี้ผู้ใช้เป็นวัตถุที่มีคุณสมบัติจากสตริง การแจ้งเตือน(ชื่อผู้ใช้.ชื่อ); // จอห์น การแจ้งเตือน ( user.age ); // 30
คุณสามารถค้นหาข้อมูลโดยละเอียดเพิ่มเติมเกี่ยวกับ JSON ได้ในวิธี JSON ถึงบท JSON
หาก json
มีรูปแบบไม่ถูกต้อง JSON.parse
จะสร้างข้อผิดพลาด ดังนั้นสคริปต์จึง "ตาย"
เราควรจะพอใจกับสิ่งนั้นไหม? ไม่แน่นอน!
ด้วยวิธีนี้ หากมีสิ่งผิดปกติเกิดขึ้นกับข้อมูล ผู้เยี่ยมชมจะไม่มีทางรู้ได้เลย (เว้นแต่พวกเขาจะเปิดคอนโซลนักพัฒนาซอฟต์แวร์) และผู้คนไม่ชอบเวลาที่บางสิ่ง “เพิ่งตาย” โดยไม่มีข้อความแสดงข้อผิดพลาดใดๆ
ลองใช้ try...catch
เพื่อจัดการกับข้อผิดพลาด:
ให้ json = "{ json ไม่ดี }"; พยายาม { ให้ผู้ใช้ = JSON.parse(json); // <-- เมื่อเกิดข้อผิดพลาด... การแจ้งเตือน (ชื่อผู้ใช้.ชื่อ); // ไม่ทำงาน } จับ (ผิดพลาด) { // ...การประหารชีวิตกระโดดมาที่นี่ alert( "ขออภัย ข้อมูลมีข้อผิดพลาด เราจะพยายามขอใหม่อีกครั้ง" ); การแจ้งเตือน (ผิดพลาดชื่อ); การแจ้งเตือน (ข้อผิดพลาดข้อความ); -
ที่นี่เราใช้ catch
block เพื่อแสดงข้อความเท่านั้น แต่เราสามารถทำได้มากกว่านี้: ส่งคำขอเครือข่ายใหม่ แนะนำทางเลือกอื่นให้กับผู้เยี่ยมชม ส่งข้อมูลเกี่ยวกับข้อผิดพลาดไปยังเครื่องมืออำนวยความสะดวกในการบันทึก … . ดีกว่าตายไปซะหมดเลย
จะเกิดอะไรขึ้นถ้า json
ถูกต้องตามหลักไวยากรณ์ แต่ไม่มีคุณสมบัติ name
ที่ต้องการ
แบบนี้:
ให้ json = '{ "อายุ": 30 }'; //ข้อมูลไม่สมบูรณ์ พยายาม { ให้ผู้ใช้ = JSON.parse(json); // <-- ไม่มีข้อผิดพลาด การแจ้งเตือน(ชื่อผู้ใช้.ชื่อ); //ไม่มีชื่อ! } จับ (ผิดพลาด) { alert( "ไม่ทำงาน" ); -
ที่นี่ JSON.parse
ทำงานตามปกติ แต่การไม่มี name
ถือเป็นข้อผิดพลาดสำหรับเรา
เพื่อรวมการจัดการข้อผิดพลาด เราจะใช้ตัวดำเนินการ throw
ตัวดำเนินการ throw
สร้างข้อผิดพลาด
ไวยากรณ์คือ:
โยน <วัตถุข้อผิดพลาด>
ในทางเทคนิคแล้ว เราสามารถใช้อะไรก็ได้ที่เป็นวัตถุแสดงข้อผิดพลาด นั่นอาจเป็นค่าพื้นฐาน เช่น ตัวเลขหรือสตริง แต่จะดีกว่าถ้าใช้อ็อบเจ็กต์ โดยควรมีคุณสมบัติ name
และ message
(เพื่อให้เข้ากันได้กับข้อผิดพลาดในตัว)
JavaScript มี Constructor ในตัวมากมายสำหรับข้อผิดพลาดมาตรฐาน: Error
, SyntaxError
, ReferenceError
, TypeError
และอื่นๆ เราสามารถใช้มันเพื่อสร้างวัตถุข้อผิดพลาดได้เช่นกัน
ไวยากรณ์ของพวกเขาคือ:
ให้ข้อผิดพลาด = ข้อผิดพลาดใหม่ (ข้อความ); // หรือ ให้ข้อผิดพลาด = ใหม่ SyntaxError (ข้อความ); ให้ข้อผิดพลาด = ใหม่ ReferenceError (ข้อความ); -
สำหรับข้อผิดพลาดในตัว (ไม่ใช่สำหรับออบเจ็กต์ใดๆ เพียงสำหรับข้อผิดพลาด) คุณสมบัติ name
จะเป็นชื่อของตัวสร้างทุกประการ และ message
ก็ถูกนำมาจากข้อโต้แย้ง
ตัวอย่างเช่น:
ให้ข้อผิดพลาด = ข้อผิดพลาดใหม่ ("สิ่งที่เกิดขึ้น o_O"); การแจ้งเตือน (ข้อผิดพลาดชื่อ); // ข้อผิดพลาด การแจ้งเตือน (ข้อผิดพลาดข้อความ); //เรื่องเกิดขึ้นo_O
มาดูกันว่า JSON.parse
สร้างข้อผิดพลาดประเภทใด:
พยายาม { JSON.parse("{ json ไม่ดี o_O }"); } จับ (ผิดพลาด) { alert(err.name); // SyntaxError การแจ้งเตือน (ข้อผิดพลาดข้อความ); // โทเค็นที่ไม่คาดคิด b ใน JSON ที่ตำแหน่ง 2 -
อย่างที่เราเห็น นั่นคือ SyntaxError
และในกรณีของเรา การไม่มี name
ถือเป็นข้อผิดพลาด เนื่องจากผู้ใช้ต้องมี name
งั้นเรามาโยนมันกันเถอะ:
ให้ json = '{ "อายุ": 30 }'; //ข้อมูลไม่สมบูรณ์ พยายาม { ให้ผู้ใช้ = JSON.parse(json); // <-- ไม่มีข้อผิดพลาด ถ้า (!user.name) { โยน SyntaxError ใหม่ ("ข้อมูลไม่สมบูรณ์: ไม่มีชื่อ"); - - การแจ้งเตือน(ชื่อผู้ใช้.ชื่อ); } จับ (ผิดพลาด) { alert( "ข้อผิดพลาด JSON: " + err.message ); // ข้อผิดพลาด JSON: ข้อมูลไม่สมบูรณ์: ไม่มีชื่อ -
ในบรรทัด (*)
ตัวดำเนิน throw
จะสร้าง SyntaxError
พร้อมกับ message
ที่กำหนด เช่นเดียวกับที่ JavaScript จะสร้างเอง การดำเนินการของ try
หยุดทันที และโฟลว์การควบคุมจะกระโดดเข้าสู่ catch
ตอนนี้ catch
กลายเป็นที่เดียวสำหรับการจัดการข้อผิดพลาดทั้งหมด: ทั้งสำหรับ JSON.parse
และกรณีอื่น ๆ
ในตัวอย่างข้างต้น เราใช้ try...catch
เพื่อจัดการข้อมูลที่ไม่ถูกต้อง แต่เป็นไปได้ไหมที่จะมี ข้อผิดพลาดที่ไม่คาดคิด เกิดขึ้นภายในบล็อก try {...}
เช่นเดียวกับข้อผิดพลาดในการเขียนโปรแกรม (ไม่ได้กำหนดตัวแปร) หรืออย่างอื่น ไม่ใช่แค่ "ข้อมูลที่ไม่ถูกต้อง" เท่านั้น
ตัวอย่างเช่น:
ให้ json = '{ "อายุ": 30 }'; //ข้อมูลไม่สมบูรณ์ พยายาม { ผู้ใช้ = JSON.parse(json); // <-- ลืมใส่ "let" นำหน้าผู้ใช้ - } จับ (ผิดพลาด) { alert("ข้อผิดพลาด JSON: " + ผิดพลาด); // ข้อผิดพลาด JSON: ReferenceError: ไม่ได้กำหนดผู้ใช้ // (จริงๆ แล้วไม่มีข้อผิดพลาด JSON) -
แน่นอนว่าทุกอย่างเป็นไปได้! โปรแกรมเมอร์มักทำผิดพลาด แม้แต่ในโปรแกรมอรรถประโยชน์โอเพ่นซอร์สที่ใช้โดยคนนับล้านมานานหลายทศวรรษ ทันใดนั้นข้อผิดพลาดก็อาจถูกค้นพบซึ่งนำไปสู่การแฮ็กที่เลวร้าย
ในกรณีของเรา try...catch
เพื่อตรวจจับข้อผิดพลาด "ข้อมูลไม่ถูกต้อง" แต่โดยธรรมชาติแล้ว catch
ได้รับข้อผิดพลาด ทั้งหมด จาก try
ที่นี่ได้รับข้อผิดพลาดที่ไม่คาดคิด แต่ยังคงแสดงข้อความ "JSON Error"
เดิม นั่นผิดและทำให้โค้ดยากขึ้นในการแก้ไขข้อบกพร่องด้วย
เพื่อหลีกเลี่ยงปัญหาดังกล่าว เราสามารถใช้เทคนิค "การรื้อใหม่" ได้ กฎนั้นง่าย:
Catch ควรประมวลผลเฉพาะข้อผิดพลาดที่รู้และ "โยน" ข้อผิดพลาดอื่นๆ ทั้งหมดเท่านั้น
เทคนิคการ “รื้อฟื้น” สามารถอธิบายได้อย่างละเอียดดังนี้:
Catch ได้รับข้อผิดพลาดทั้งหมด
ในบล็อก catch (err) {...}
เราวิเคราะห์วัตถุข้อผิดพลาด err
หากเราไม่ทราบวิธีจัดการกับมัน เราจะ throw err
โดยปกติแล้ว เราสามารถตรวจสอบประเภทข้อผิดพลาดได้โดยใช้ตัวดำเนินการ instanceof
:
พยายาม { ผู้ใช้ = { /*...*/ }; } จับ (ผิดพลาด) { ถ้า (ข้อผิดพลาดของ ReferenceError) { alert('ข้อผิดพลาดอ้างอิง'); // "ReferenceError" สำหรับการเข้าถึงตัวแปรที่ไม่ได้กำหนด - -
เรายังสามารถรับชื่อคลาสข้อผิดพลาดได้จากคุณสมบัติ err.name
ข้อผิดพลาดดั้งเดิมทั้งหมดมีอยู่ อีกทางเลือกหนึ่งคืออ่าน err.constructor.name
ในโค้ดด้านล่าง เราใช้การโยนใหม่เพื่อให้ catch
จัดการเฉพาะ SyntaxError
:
ให้ json = '{ "อายุ": 30 }'; //ข้อมูลไม่สมบูรณ์ พยายาม { ให้ผู้ใช้ = JSON.parse(json); ถ้า (!user.name) { โยน SyntaxError ใหม่ ("ข้อมูลไม่สมบูรณ์: ไม่มีชื่อ"); - บลาบลา(); // เกิดข้อผิดพลาดที่ไม่คาดคิด การแจ้งเตือน(ชื่อผู้ใช้.ชื่อ); } จับ (ผิดพลาด) { ถ้า (ข้อผิดพลาดของ SyntaxError) { alert( "ข้อผิดพลาด JSON: " + err.message ); } อื่น { โยนผิดพลาด; // โยนใหม่ (*) - -
ข้อผิดพลาดที่ส่งบนบรรทัด (*)
จาก catch
block ภายใน "หลุดออก" ของ try...catch
และสามารถจับได้โดยโครงสร้าง try...catch
ภายนอก (ถ้ามี) หรือฆ่าสคริปต์
ดังนั้น catch
block จริงๆ จะจัดการเฉพาะข้อผิดพลาดที่รู้วิธีจัดการและ "ข้าม" ข้อผิดพลาดอื่นๆ ทั้งหมด
ตัวอย่างด้านล่างแสดงให้เห็นว่าสามารถตรวจจับข้อผิดพลาดดังกล่าวได้โดย try...catch
อีกระดับหนึ่ง:
ฟังก์ชั่น readData() { ให้ json = '{ "อายุ": 30 }'; พยายาม { - บลาบลา(); // ข้อผิดพลาด! } จับ (ผิดพลาด) { - ถ้า (!(ข้อผิดพลาดของอินสแตนซ์ของ SyntaxError)) { โยนผิดพลาด; // โยนใหม่ (ไม่รู้จะจัดการยังไง) - - - พยายาม { อ่านข้อมูล(); } จับ (ผิดพลาด) { alert( "การจับภายนอกได้: " + err ); //จับได้แล้ว! -
ที่นี่ readData
รู้วิธีจัดการ SyntaxError
เท่านั้น ในขณะที่ try...catch
ภายนอกรู้วิธีจัดการทุกอย่าง
รอ นั่นไม่ใช่ทั้งหมด
โครงสร้าง try...catch
อาจมีโค้ดอีกหนึ่งประโยค: finally
หากมีอยู่ มันจะทำงานในทุกกรณี:
หลังจาก try
แล้วหากไม่มีข้อผิดพลาด
หลังจาก catch
หากมีข้อผิดพลาด
ไวยากรณ์เพิ่มเติมมีลักษณะดังนี้:
พยายาม { ... พยายามรันโค้ด ... } จับ (ผิดพลาด) { ... จัดการกับข้อผิดพลาด ... } ในที่สุด { ... ดำเนินการเสมอ ... -
ลองเรียกใช้รหัสนี้:
พยายาม { alert( 'ลอง' ); if (confirm('สร้างข้อผิดพลาด?')) BAD_CODE(); } จับ (ผิดพลาด) { alert( 'จับ' ); } ในที่สุด { alert( 'ในที่สุด' ); -
รหัสมีสองวิธีในการดำเนินการ:
หากคุณตอบว่า "ใช่" เป็น "สร้างข้อผิดพลาด" ให้ try -> catch -> finally
หากคุณพูดว่า "ไม่" ให้ try -> finally
finally
clause มักใช้เมื่อเราเริ่มทำอะไรบางอย่างและต้องการจบมันไม่ว่าในกรณีใดก็ตามของผลลัพธ์
ตัวอย่างเช่น เราต้องการวัดเวลาที่ฟังก์ชันตัวเลข Fibonacci ใช้ fib(n)
แน่นอนว่าเราสามารถเริ่มวัดก่อนที่จะวิ่งและวัดให้เสร็จทีหลังได้ แต่จะเกิดอะไรขึ้นถ้ามีข้อผิดพลาดระหว่างการเรียกใช้ฟังก์ชัน? โดยเฉพาะอย่างยิ่ง การใช้ fib(n)
ในโค้ดด้านล่างจะส่งคืนข้อผิดพลาดสำหรับตัวเลขที่เป็นลบหรือไม่ใช่จำนวนเต็ม
ประโยค finally
เป็นสถานที่ที่ดีในการวัดให้เสร็จสิ้นไม่ว่าจะเกิดอะไรขึ้นก็ตาม
ใน finally
ที่นี่รับประกันว่าเวลาจะถูกวัดอย่างถูกต้องในทั้งสองสถานการณ์ - ในกรณีที่การดำเนินการ fib
สำเร็จและในกรณีที่เกิดข้อผิดพลาด:
ให้ num = +prompt("ป้อนจำนวนเต็มบวก?", 35) ปล่อยให้แตกต่างผล; ฟังก์ชั่นปลิ้นปล้อน (n) { ถ้า (n < 0 || Math.trunc(n) != n) { Throw new Error("ต้องไม่เป็นค่าลบและเป็นจำนวนเต็มด้วย"); - กลับ n <= 1 ? n : ตอแหล(n - 1) + ตอแหล(n - 2); - เริ่มกันเลย = Date.now(); พยายาม { ผลลัพธ์ = ปลิ้นปล้อน (หมายเลข); } จับ (ผิดพลาด) { ผลลัพธ์ = 0; } ในที่สุด { diff = Date.now() - เริ่มต้น; - alert(ผลลัพธ์ || "เกิดข้อผิดพลาด"); alert( `การดำเนินการใช้เวลา ${diff}ms` );
คุณสามารถตรวจสอบได้โดยการรันโค้ดโดยป้อน 35
ลงใน prompt
ซึ่งจะทำงานได้ตามปกติ และ finally
หลังจาก try
จากนั้นป้อน -1
– จะมีข้อผิดพลาดทันที และการดำเนินการจะใช้เวลา 0ms
การวัดทั้งสองทำอย่างถูกต้อง
กล่าวอีกนัยหนึ่ง ฟังก์ชันอาจจบด้วย return
หรือ throw
ซึ่งไม่สำคัญ ส่วนคำสั่ง finally
ดำเนินการในทั้งสองกรณี
ตัวแปรอยู่ภายใน try...catch...finally
โปรดทราบว่า result
และความ diff
ต่างในโค้ดด้านบนจะถูกประกาศ ก่อน try...catch
มิฉะนั้น หากเราประกาศ let
try
บล็อกนั้นจะมองเห็นได้เฉพาะภายในบล็อกเท่านั้น
finally
และ return
ส่วนคำสั่ง finally
ใช้สำหรับ การ ออกจาก try...catch
รวมถึง return
ที่ชัดเจน
ในตัวอย่างด้านล่าง มี return
เป็น try
ในกรณีนี้ finally
จะถูกดำเนินการก่อนที่การควบคุมจะกลับสู่โค้ดภายนอก
ฟังก์ชั่น func() { พยายาม { กลับ 1; } จับ (ผิดพลาด) { - } ในที่สุด { alert( 'ในที่สุด' ); - - การแจ้งเตือน( func() ); // งานแรกแจ้งเตือนจากในที่สุดแล้วอันนี้
try...finally
try...finally
build โดยไม่มี catch
clause ก็มีประโยชน์เช่นกัน เราใช้เมื่อเราไม่ต้องการจัดการกับข้อผิดพลาดที่นี่ (ปล่อยให้ข้อผิดพลาดผ่านไป) แต่ต้องการให้แน่ใจว่ากระบวนการที่เราเริ่มต้นนั้นได้รับการสรุปแล้ว
ฟังก์ชั่น func() { // เริ่มทำสิ่งที่ต้องทำให้เสร็จ (เช่นการวัด) พยายาม { - } ในที่สุด { // ทำสิ่งนั้นให้สำเร็จแม้ว่าทุกคนจะตายก็ตาม - -
ในโค้ดด้านบน ข้อผิดพลาดภายใน try
มักจะหลุดออกไป เนื่องจากไม่มี catch
แต่ finally
ก็ใช้งานได้ก่อนที่โฟลว์การดำเนินการจะออกจากฟังก์ชัน
เฉพาะสิ่งแวดล้อม
ข้อมูลจากส่วนนี้ไม่ได้เป็นส่วนหนึ่งของ JavaScript หลัก
สมมติว่าเรามีข้อผิดพลาดร้ายแรงนอกเหนือจาก try...catch
และสคริปต์ก็หยุดทำงาน เช่นข้อผิดพลาดในการเขียนโปรแกรมหรือเรื่องเลวร้ายอื่นๆ
มีวิธีตอบสนองต่อเหตุการณ์ดังกล่าวหรือไม่? เราอาจต้องการบันทึกข้อผิดพลาด แสดงบางอย่างให้ผู้ใช้เห็น (โดยปกติพวกเขาจะไม่เห็นข้อความแสดงข้อผิดพลาด) ฯลฯ
ไม่มีในข้อกำหนด แต่สภาพแวดล้อมมักจะจัดเตรียมไว้ให้ เพราะมันมีประโยชน์จริงๆ ตัวอย่างเช่น Node.js มี process.on("uncaughtException")
สำหรับสิ่งนั้น และในเบราว์เซอร์ เราสามารถกำหนดฟังก์ชันให้กับคุณสมบัติพิเศษ window.onerror ซึ่งจะทำงานในกรณีที่เกิดข้อผิดพลาดที่ตรวจไม่พบ
ไวยากรณ์:
window.onerror = function (ข้อความ, url, บรรทัด, col, ข้อผิดพลาด) { - -
message
ข้อความแสดงข้อผิดพลาด
url
URL ของสคริปต์ที่เกิดข้อผิดพลาด
line
, col
หมายเลขบรรทัดและคอลัมน์ที่เกิดข้อผิดพลาด
error
วัตถุมีข้อผิดพลาด
ตัวอย่างเช่น:
<สคริปต์> window.onerror = function (ข้อความ, url, บรรทัด, col, ข้อผิดพลาด) { alert(`${message}n At ${line}:${col} จาก ${url}`); - ฟังก์ชั่น readData() { badFunc(); // อ๊ะ มีบางอย่างผิดพลาด! - อ่านข้อมูล(); </สคริปต์>
บทบาทของตัวจัดการส่วนกลาง window.onerror
ไม่ใช่การกู้คืนการเรียกใช้สคริปต์ ซึ่งอาจเป็นไปไม่ได้ในกรณีที่เกิดข้อผิดพลาดในการเขียนโปรแกรม แต่จะส่งข้อความแสดงข้อผิดพลาดไปยังนักพัฒนา
นอกจากนี้ยังมีบริการบนเว็บที่ให้การบันทึกข้อผิดพลาดสำหรับกรณีดังกล่าว เช่น https://errorception.com หรือ https://www.muscula.com
พวกเขาทำงานเช่นนี้:
เราลงทะเบียนที่บริการและรับชิ้นส่วนของ JS (หรือ URL สคริปต์) จากพวกเขาเพื่อแทรกลงในเพจ
สคริปต์ JS นั้นตั้งค่าฟังก์ชัน window.onerror
แบบกำหนดเอง
เมื่อเกิดข้อผิดพลาด ระบบจะส่งคำขอเครือข่ายเกี่ยวกับข้อผิดพลาดดังกล่าวไปยังบริการ
เราสามารถเข้าสู่บริการเว็บอินเตอร์เฟสและดูข้อผิดพลาดได้
โครงสร้าง try...catch
ช่วยให้สามารถจัดการข้อผิดพลาดรันไทม์ได้ อนุญาตให้ "ลอง" เรียกใช้โค้ดและข้อผิดพลาด "จับ" ที่อาจเกิดขึ้นได้อย่างแท้จริง
ไวยากรณ์คือ:
พยายาม { // รันโค้ดนี้ } จับ (ผิดพลาด) { // หากเกิดข้อผิดพลาด ให้ข้ามมาที่นี่ // err เป็นวัตถุข้อผิดพลาด } ในที่สุด { // ทำทุกกรณีหลังจาก try/catch -
อาจไม่มีส่วน catch
หรือ no finally
ดังนั้นโครงสร้างที่สั้นกว่า try...catch
and try...finally
ก็ใช้ได้เช่นกัน
วัตถุข้อผิดพลาดมีคุณสมบัติดังต่อไปนี้:
message
– ข้อความแสดงข้อผิดพลาดที่มนุษย์สามารถอ่านได้
name
– สตริงที่มีชื่อข้อผิดพลาด (ชื่อตัวสร้างข้อผิดพลาด)
stack
(ไม่ได้มาตรฐาน แต่ได้รับการสนับสนุนอย่างดี) - สแต็กในขณะที่สร้างข้อผิดพลาด
หากไม่ต้องการวัตถุที่มีข้อผิดพลาด เราสามารถละเว้นได้โดยใช้ catch {
แทน catch (err) {
เรายังสามารถสร้างข้อผิดพลาดของเราเองได้โดยใช้ตัวดำเนินการ throw
ในทางเทคนิค อาร์กิวเมนต์ของ throw
สามารถเป็นอะไรก็ได้ แต่โดยปกติแล้วจะเป็นอ็อบเจ็กต์ข้อผิดพลาดที่สืบทอดมาจากคลาส Error
ในตัว ข้อมูลเพิ่มเติมเกี่ยวกับการขยายข้อผิดพลาดในบทถัดไป
การโยนซ้ำ เป็นรูปแบบที่สำคัญมากของการจัดการข้อผิดพลาด: catch
block มักจะคาดหวังและรู้วิธีจัดการกับข้อผิดพลาดประเภทนั้น ดังนั้นจึงควรโยนข้อผิดพลาดที่ไม่ทราบอีกครั้ง
แม้ว่าเราจะไม่ได้ try...catch
แต่สภาพแวดล้อมส่วนใหญ่อนุญาตให้เราตั้งค่าตัวจัดการข้อผิดพลาด “ส่วนกลาง” เพื่อตรวจจับข้อผิดพลาดที่ “หลุดออกไป” ในเบราว์เซอร์นั่นคือ window.onerror
ความสำคัญ: 5
เปรียบเทียบสองส่วนของโค้ด
อันแรกใช้ finally
เพื่อรันโค้ดหลังจาก try...catch
:
พยายาม { ทำงาน } จับ (ผิดพลาด) { จัดการกับข้อผิดพลาด } ในที่สุด { ทำความสะอาดพื้นที่ทำงาน -
ส่วนที่สองทำให้การทำความสะอาดทันทีหลังจาก try...catch
:
พยายาม { ทำงาน } จับ (ผิดพลาด) { จัดการกับข้อผิดพลาด - ทำความสะอาดพื้นที่ทำงาน
เราต้องการการทำความสะอาดหลังเลิกงานอย่างแน่นอน ไม่ว่าจะมีข้อผิดพลาดหรือไม่ก็ตาม
มีข้อได้เปรียบในการใช้ finally
หรือทั้งสองส่วนโค้ดเท่ากันหรือไม่ หากมีข้อได้เปรียบดังกล่าวก็ควรยกตัวอย่างเมื่อเป็นเรื่องสำคัญ
ความแตกต่างจะชัดเจนเมื่อเราดูโค้ดภายในฟังก์ชัน
พฤติกรรมจะแตกต่างออกไปหากมีการ "กระโดดออก" try...catch
ตัวอย่างเช่นเมื่อมี return
ภายใน try...catch
ส่วนคำสั่ง finally
ใช้งานได้ในกรณีที่ มี การออกจาก try...catch
แม้ว่าจะผ่านคำสั่ง return
ก็ตาม: ทันทีหลังจากที่ try...catch
เสร็จสิ้น แต่ก่อนที่โค้ดการโทรจะได้รับการควบคุม
ฟังก์ชัน ฉ() { พยายาม { alert('เริ่ม'); กลับ "ผลลัพธ์"; } จับ (ผิดพลาด) { - } ในที่สุด { alert('ล้างข้อมูล!'); - - ฉ(); // ทำความสะอาด!
…หรือเมื่อมี throw
เช่นที่นี่:
ฟังก์ชัน ฉ() { พยายาม { alert('เริ่ม'); โยนข้อผิดพลาดใหม่ ("ข้อผิดพลาด"); } จับ (ผิดพลาด) { - if("ไม่สามารถจัดการข้อผิดพลาดได้") { โยนผิดพลาด; - } ในที่สุด { alert('ล้างข้อมูล!') - - ฉ(); // ทำความสะอาด!
finally
ก็รับประกันการทำความสะอาดที่นี่ ถ้าเราใส่โค้ดไว้ท้าย f
มันก็จะไม่ทำงานในสถานการณ์เหล่านี้