คำเตือน
ข้อเสนอนี้จะเปลี่ยนเป็นการ try-expressions
อย่างมีความหมายว่าเป็นสิ่งที่เป็นสำนวนมากขึ้นสำหรับปัญหานี้ อ่านเพิ่มเติมเกี่ยวกับ #4 และ #5
ความช่วยเหลือเกี่ยวกับการเขียนใหม่เป็นสิ่งจำเป็น :)
คำเตือน
ข้อเสนอนี้อยู่ระหว่างการพัฒนาอย่างแข็งขันและยินดีต้อนรับการมีส่วนร่วม
ข้อเสนอนี้แนะนำตัวดำเนินการใหม่ ?=
(การมอบหมายที่ปลอดภัย) ซึ่งทำให้การจัดการข้อผิดพลาดง่ายขึ้นโดยการเปลี่ยนผลลัพธ์ของฟังก์ชั่นเป็น tuple หากฟังก์ชั่นส่งข้อผิดพลาดตัวดำเนินการจะส่งคืน [error, null]
; หากฟังก์ชั่นดำเนินการสำเร็จจะส่งคืน [null, result]
ตัวดำเนินการนี้เข้ากันได้กับสัญญาฟังก์ชั่น async และค่าใด ๆ ที่ใช้ Symbol.result
ตัวอย่างเช่นเมื่อทำการดำเนินการ I/O หรือโต้ตอบกับ APIs ตามสัญญาข้อผิดพลาดสามารถเกิดขึ้นได้อย่างไม่คาดคิดเมื่อรันไทม์ การละเลยที่จะจัดการกับข้อผิดพลาดเหล่านี้อาจนำไปสู่พฤติกรรมที่ไม่ได้ตั้งใจและช่องโหว่ด้านความปลอดภัยที่อาจเกิดขึ้น
const [ error , response ] ? = await fetch ( "https://arthur.place" )
Symbol.result
?=
)using
คำสั่งdata
ก่อน??=
กับฟังก์ชั่นและวัตถุที่ไม่มี Symbol.result
คุณเห็นรหัสเช่นนี้บ่อยแค่ไหน?
async function getData ( ) {
const response = await fetch ( "https://api.example.com/data" )
const json = await response . json ( )
return validationSchema . parse ( json )
}
ปัญหาเกี่ยวกับฟังก์ชั่นข้างต้นคือมันอาจล้มเหลวอย่างเงียบ ๆ อาจทำให้โปรแกรมของคุณล้มเหลวโดยไม่มีการเตือนใด ๆ อย่างชัดเจน
fetch
สามารถปฏิเสธได้json
สามารถปฏิเสธได้parse
สามารถโยนได้ ในการแก้ไขปัญหานี้เราเสนอการยอมรับผู้ให้บริการใหม่ ?=
ซึ่งอำนวยความสะดวกในการจัดการข้อผิดพลาดที่กระชับและอ่านได้มากขึ้น
async function getData ( ) {
const [ requestError , response ] ? = await fetch (
"https://api.example.com/data"
)
if ( requestError ) {
handleRequestError ( requestError )
return
}
const [ parseError , json ] ? = await response . json ( )
if ( parseError ) {
handleParseError ( parseError )
return
}
const [ validationError , data ] ? = validationSchema . parse ( json )
if ( validationError ) {
handleValidationError ( validationError )
return
}
return data
}
โปรดดูสิ่งที่ข้อเสนอนี้ไม่ได้มีจุดมุ่งหมายเพื่อแก้ไขส่วนเพื่อทำความเข้าใจข้อ จำกัด ของข้อเสนอนี้
ข้อเสนอนี้มีวัตถุประสงค์เพื่อแนะนำคุณสมบัติต่อไปนี้:
Symbol.result
วัตถุใด ๆ ที่ใช้ Symbol.result
วิธีการ result สามารถใช้กับตัวดำเนินการ ?=
function example ( ) {
return {
[ Symbol . result ] ( ) {
return [ new Error ( "123" ) , null ]
} ,
}
}
const [ error , result ] ? = example ( ) // Function.prototype also implements Symbol.result
// const [error, result] = example[Symbol.result]()
// error is Error('123')
Symbol.result
วิธีจะต้องส่งคืน tuple โดยที่องค์ประกอบแรกแสดงถึงข้อผิดพลาดและองค์ประกอบที่สองแสดงถึงผลลัพธ์
ทำไมไม่ data
ก่อน?
?=
) ตัวดำเนินการ ?=
ผู้ประกอบการเรียกใช้ Symbol.result
วิธีการ result บนวัตถุหรือฟังก์ชั่นทางด้านขวาของผู้ปฏิบัติงานเพื่อให้แน่ใจว่าข้อผิดพลาดและผลลัพธ์ได้รับการจัดการอย่างต่อเนื่องในลักษณะที่มีโครงสร้าง
const obj = {
[ Symbol . result ] ( ) {
return [ new Error ( "Error" ) , null ]
} ,
}
const [ error , data ] ? = obj
// const [error, data] = obj[Symbol.result]()
function action ( ) {
return 'data'
}
const [ error , data ] ? = action ( argument )
// const [error, data] = action[Symbol.result](argument)
ผลลัพธ์ควรสอดคล้องกับรูปแบบ [error, null | undefined]
หรือ [null, data]
เมื่อใช้งาน ?=
ตัวดำเนินการภายในฟังก์ชั่นพารามิเตอร์ทั้งหมดที่ส่งผ่านไปยังฟังก์ชันนั้นจะถูกส่งต่อไปยัง Symbol.result
Method
declare function action ( argument : string ) : string
const [ error , data ] ? = action ( argument1 , argument2 , ... )
// const [error, data] = action[Symbol.result](argument, argument2, ...)
เมื่อใช้งาน ?=
ตัวดำเนินการกับวัตถุจะไม่มีการส่งพารามิเตอร์ไปยัง Symbol.result
Method
declare const obj : { [ Symbol . result ] : ( ) => any }
const [ error , data ] ? = obj
// const [error, data] = obj[Symbol.result]()
[error, null]
tuple ถูกสร้างขึ้นเมื่อเกิดข้อผิดพลาดครั้งแรก อย่างไรก็ตามหาก data
ใน [null, data]
tuple ยังใช้ Symbol.result
วิธีการ result มันจะถูกเรียกใช้ซ้ำ
const obj = {
[ Symbol . result ] ( ) {
return [
null ,
{
[ Symbol . result ] ( ) {
return [ new Error ( "Error" ) , null ]
} ,
} ,
]
} ,
}
const [ error , data ] ? = obj
// const [error, data] = obj[Symbol.result]()
// error is Error('string')
พฤติกรรมเหล่านี้อำนวยความสะดวกในการจัดการสถานการณ์ต่าง ๆ ที่เกี่ยวข้องกับสัญญาหรือวัตถุที่ Symbol.result
วิธีการ:
async function(): Promise<T>
function(): T
function(): T | Promise<T>
กรณีเหล่านี้อาจเกี่ยวข้องกับวัตถุที่ซ้อนกัน 0 ถึง 2 ระดับที่มี Symbol.result
วิธีการ result และผู้ปฏิบัติงานได้รับการออกแบบมาเพื่อจัดการทั้งหมดอย่างถูกต้อง
Promise
คือการใช้งานอื่น ๆ เท่านั้นนอกเหนือจาก Function
ที่สามารถใช้กับตัวดำเนินการ ?=
const promise = getPromise ( )
const [ error , data ] ? = await promise
// const [error, data] = await promise[Symbol.result]()
คุณอาจสังเกตเห็นว่า await
?=
สามารถใช้ร่วมกันได้และนั่นก็ดี เนื่องจากคุณสมบัติการจัดการแบบเรียกซ้ำจึงไม่มีปัญหาในการรวมเข้าด้วยกันด้วยวิธีนี้
const [ error , data ] ? = await getPromise ( )
// const [error, data] = await getPromise[Symbol.result]()
การดำเนินการจะเป็นไปตามคำสั่งนี้:
getPromise[Symbol.result]()
อาจโยนข้อผิดพลาดเมื่อเรียกว่า (ถ้าเป็นฟังก์ชันซิงโครนัสที่ส่งคืนสัญญา)error
และการดำเนินการจะหยุดลงdata
เนื่องจาก data
เป็นคำสัญญาและสัญญามี Symbol.result
วิธีการ result จึงจะได้รับการจัดการซ้ำerror
และการดำเนินการจะหยุดdata
using
คำสั่ง คำสั่ง using
หรือ await using
ควรทำงานกับตัวดำเนินการ ?=
มันจะดำเนินการคล้ายกับมาตรฐาน using x = y
โปรดทราบว่าข้อผิดพลาดที่เกิดขึ้นเมื่อการกำจัดทรัพยากรไม่ได้ถูกจับโดยผู้ดำเนินการ ?=
เช่นเดียวกับที่พวกเขาไม่ได้รับการจัดการโดยคุณสมบัติปัจจุบันอื่น ๆ
try {
using a = b
} catch ( error ) {
// handle
}
// now becomes
using [ error , a ] ? = b
// or with async
try {
await using a = b
} catch ( error ) {
// handle
}
// now becomes
await using [ error , a ] ? = b
การ using
จะถูกนำไปใช้เฉพาะเมื่อ error
เป็น null
หรือ undefined
และ a
คือความจริงและมี Symbol.dispose
วิธีการ
บล็อก try {}
ไม่ค่อยมีประโยชน์เนื่องจากการกำหนดขอบเขตขาดความสำคัญทางแนวคิด มันมักจะทำหน้าที่เป็นคำอธิบายประกอบของรหัสมากกว่าการสร้างกระแสควบคุม ซึ่งแตกต่างจากบล็อกโฟลว์ควบคุมไม่มีสถานะโปรแกรมที่มีความหมายเฉพาะภายในบล็อก try {}
ในทางตรงกันข้ามบล็อก catch {}
คือ การควบคุมการควบคุมจริงและการกำหนดขอบเขตนั้นมีความหมายและเกี่ยวข้อง
การใช้บล็อก try/catch
มี สองปัญหาไวยากรณ์หลัก :
// Nests 1 level for each error handling block
async function readData ( filename ) {
try {
const fileContent = await fs . readFile ( filename , "utf8" )
try {
const json = JSON . parse ( fileContent )
return json . data
} catch ( error ) {
handleJsonError ( error )
return
}
} catch ( error ) {
handleFileError ( error )
return
}
}
// Declares reassignable variables outside the block, which is undesirable
async function readData ( filename ) {
let fileContent
let json
try {
fileContent = await fs . readFile ( filename , "utf8" )
} catch ( error ) {
handleFileError ( error )
return
}
try {
json = JSON . parse ( fileContent )
} catch ( error ) {
handleJsonError ( error )
return
}
return json . data
}
data
ก่อน? ในการประชุมการประชุมคือการวางตัวแปรข้อมูลก่อนและคุณอาจสงสัยว่าทำไมเราไม่ปฏิบัติตามวิธีการเดียวกันใน JavaScript ในการเดินทางนี่เป็นวิธีมาตรฐานในการเรียกใช้ฟังก์ชัน อย่างไรก็ตามใน JavaScript เรามีตัวเลือกในการใช้ const data = fn()
และเลือกที่จะเพิกเฉยต่อข้อผิดพลาดซึ่งเป็นปัญหาที่เราพยายามแก้ไขอย่างแม่นยำ
หากมีคนใช้ ?=
เป็นผู้ดำเนินการที่ได้รับมอบหมายนั่นเป็นเพราะพวกเขาต้องการให้แน่ใจว่าพวกเขาจัดการกับข้อผิดพลาดและหลีกเลี่ยงการลืมพวกเขา การวางข้อมูลก่อนจะขัดแย้งกับหลักการนี้เนื่องจากมันจัดลำดับความสำคัญของผลลัพธ์มากกว่าการจัดการข้อผิดพลาด
// ignores errors!
const data = fn ( )
// Look how simple it is to forget to handle the error
const [ data ] ? = fn ( )
// This is the way to go
const [ error , data ] ? = fn ( )
หากคุณต้องการระงับข้อผิดพลาด (ซึ่ง แตกต่าง จากการเพิกเฉยต่อความเป็นไปได้ของฟังก์ชั่นการขว้างข้อผิดพลาด) คุณสามารถทำสิ่งต่อไปนี้:
// This suppresses the error (ignores it and doesn't re-throw it)
const [ , data ] ? = fn ( )
วิธีการนี้มีความชัดเจนและอ่านได้มากขึ้นเพราะยอมรับว่าอาจมีข้อผิดพลาด แต่บ่งชี้ว่าคุณไม่สนใจมัน
วิธีการข้างต้นเป็นที่รู้จักกันว่า "ลองจับคาลาโบะ" (คำศัพท์บราซิล) และสามารถเขียนใหม่เป็น:
let data
try {
data = fn ( )
} catch { }
การสนทนาที่สมบูรณ์เกี่ยวกับหัวข้อนี้ที่ #13 หากผู้อ่านสนใจ
ข้อเสนอนี้สามารถถูก polyfilled โดยใช้รหัสที่ให้ไว้ที่ polyfill.js
อย่างไรก็ตามตัวดำเนินการ ?=
ตัวเองไม่สามารถทำโพลีฟิลได้โดยตรง เมื่อกำหนดเป้าหมายสภาพแวดล้อม JavaScript รุ่นเก่าควรใช้โพสต์โปรเซสเซอร์เพื่อแปลงตัวดำเนินการ ?=
ตัวดำเนินการเป็นสาย [Symbol.result]
ที่สอดคล้องกัน
const [ error , data ] ? = await asyncAction ( arg1 , arg2 )
// should become
const [ error , data ] = await asyncAction [ Symbol . result ] ( arg1 , arg2 )
const [ error , data ] ? = action ( )
// should become
const [ error , data ] = action [ Symbol . result ] ( )
const [ error , data ] ? = obj
// should become
const [ error , data ] = obj [ Symbol . result ] ( )
?=
กับฟังก์ชั่นและวัตถุที่ไม่มี Symbol.result
หากฟังก์ชั่นหรือวัตถุไม่ใช้ Symbol.result
ตัวดำเนินการ ?=
ควรโยน TypeError
ตัวดำเนินการ ?=
ตัวดำเนินการและ Symbol.result
ไม่แนะนำตรรกะใหม่กับภาษา ในความเป็นจริงทุกสิ่งที่ข้อเสนอนี้มีจุดมุ่งหมายเพื่อให้บรรลุสามารถทำได้ด้วยปัจจุบันแม้ว่าจะ มีคุณสมบัติภาษา verbose และข้อผิดพลาด
try {
// try expression
} catch ( error ) {
// catch code
}
// or
promise // try expression
. catch ( ( error ) => {
// catch code
} )
เทียบเท่ากับ:
const [ error , data ] ? = expression
if ( error ) {
// catch code
} else {
// try code
}
รูปแบบนี้มีสถาปัตยกรรมในหลายภาษา:
?
ผู้ดำเนินการResult
try?
ผู้ดำเนินการtry
คำหลัก ในขณะที่ข้อเสนอนี้ไม่สามารถเสนอระดับความปลอดภัยหรือความเข้มงวดในระดับเดียวกันกับภาษาเหล่านี้ - เนื่องจากธรรมชาติของ JavaScript และความจริงที่ว่าคำสั่ง throw
สามารถโยนอะไรก็ได้
การบังคับใช้ประเภทที่เข้มงวดสำหรับข้อผิดพลาด : คำสั่ง throw
ในจาวาสคริปต์สามารถโยนค่าใด ๆ ข้อเสนอนี้ไม่ได้กำหนดประเภทความปลอดภัยในการจัดการข้อผิดพลาดและจะไม่แนะนำประเภทในภาษา นอกจากนี้ยังจะไม่ถูกขยายไปยัง TypeScript สำหรับข้อมูลเพิ่มเติมดู Microsoft/TypeScript#13219
การจัดการข้อผิดพลาดอัตโนมัติ : ในขณะที่ข้อเสนอนี้อำนวยความสะดวกในการจัดการข้อผิดพลาด แต่ก็ไม่สามารถจัดการกับข้อผิดพลาดได้โดยอัตโนมัติ คุณจะต้องเขียนรหัสที่จำเป็นเพื่อจัดการข้อผิดพลาด ข้อเสนอมีจุดมุ่งหมายเพื่อให้กระบวนการนี้ง่ายขึ้นและสอดคล้องกันมากขึ้น
ในขณะที่ข้อเสนอนี้ยังอยู่ในช่วงเริ่มต้นเราตระหนักถึงข้อ จำกัด และพื้นที่หลายประการที่ต้องการการพัฒนาเพิ่มเติม:
ระบบการตั้งชื่อสำหรับ Symbol.result
วิธี : เราจำเป็นต้องสร้างคำศัพท์สำหรับวัตถุและฟังก์ชั่นที่ใช้ Symbol.result
วิธีการ ข้อกำหนดที่เป็นไปได้รวมถึง ผลลัพธ์ หรือ ผิดพลาดได้ แต่ต้องมีการกำหนดสิ่งนี้
การใช้งาน this
: พฤติกรรมของ this
ภายในบริบทของ Symbol.result
รักษายังไม่ได้รับการทดสอบหรือบันทึกไว้ นี่คือพื้นที่ที่ต้องมีการสำรวจและเอกสารเพิ่มเติม
การจัดการบล็อก finally
: ขณะนี้ไม่มีการปรับปรุงไวยากรณ์สำหรับการจัดการ finally
บล็อก อย่างไรก็ตามคุณยังสามารถใช้บล็อก finally
ตามปกติ:
try {
// try code
} catch {
// catch errors
} finally {
// finally code
}
// Needs to be done as follows
const [ error , data ] ? = action ( )
try {
if ( error ) {
// catch errors
} else {
// try code
}
} finally {
// finally code
}
ข้อเสนอนี้อยู่ในช่วงเริ่มต้นและเรายินดีต้อนรับข้อมูลของคุณเพื่อช่วยปรับแต่ง โปรดอย่าลังเลที่จะเปิดปัญหาหรือส่งคำขอดึงพร้อมคำแนะนำของคุณ
ยินดีต้อนรับการบริจาคใด ๆ !
tuple-it
NPM ซึ่งแนะนำแนวคิดที่คล้ายกัน แต่ปรับเปลี่ยนต้นแบบ Promise
และ Function
-วิธีการที่เหมาะน้อยกว่าข้อเสนอนี้ได้รับใบอนุญาตภายใต้ใบอนุญาต MIT